MapLeaflet.client.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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 { type ComputedRef, defineProps, type PropType } from 'vue'
  50. import { LatLng, type PointTuple } from 'leaflet'
  51. import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
  52. import UrlUtils from '~/services/utils/urlUtils'
  53. import type { AnyJson, CollectionResponsePromise } from '~/types/data'
  54. import Country from '~/models/Core/Country'
  55. import { useEntityManager } from '~/composables/data/useEntityManager'
  56. const props = defineProps({
  57. latitude: {
  58. type: Number as PropType<number | null>,
  59. required: true,
  60. },
  61. longitude: {
  62. type: Number as PropType<number | null>,
  63. required: true,
  64. },
  65. streetAddress: {
  66. type: String as PropType<string | null>,
  67. required: false,
  68. default: null,
  69. },
  70. streetAddressSecond: {
  71. type: String as PropType<string | null>,
  72. required: false,
  73. default: null,
  74. },
  75. streetAddressThird: {
  76. type: String as PropType<string | null>,
  77. required: false,
  78. default: null,
  79. },
  80. postalCode: {
  81. type: String as PropType<string | null>,
  82. required: false,
  83. default: null,
  84. },
  85. addressCity: {
  86. type: String as PropType<string | null>,
  87. required: false,
  88. default: null,
  89. },
  90. addressCountryId: {
  91. type: Number as PropType<number | null>,
  92. required: false,
  93. default: null,
  94. },
  95. searchButton: {
  96. type: Boolean,
  97. required: false,
  98. default: false,
  99. },
  100. readonly: {
  101. type: Boolean,
  102. required: false,
  103. default: false,
  104. },
  105. })
  106. const FRANCE_LATITUDE = 46.603354
  107. const FRANCE_LONGITUDE = 1.888334
  108. const { apiRequestService, pending } = useAp2iRequestService()
  109. const { em } = useEntityManager()
  110. const position: ComputedRef<PointTuple> = computed(() => {
  111. return [
  112. props.latitude || FRANCE_LATITUDE,
  113. props.longitude || FRANCE_LONGITUDE,
  114. ]
  115. })
  116. const zoom = computed({
  117. get() {
  118. return props.latitude && props.latitude != FRANCE_LATITUDE ? 12 : 5
  119. },
  120. set(newValue: string) {
  121. zoom.value = newValue
  122. },
  123. })
  124. const emit = defineEmits(['update:latitude', 'update:longitude'])
  125. const onPositionUpdate = (event: LatLng): void => {
  126. emit('update:latitude', event.lat)
  127. emit('update:longitude', event.lng)
  128. }
  129. const gpsResponses: Ref<Array<AnyJson>> = ref([])
  130. const search = async () => {
  131. gpsResponses.value = []
  132. const baseUrl = UrlUtils.join('api', 'gps-coordinate-searching')
  133. const query: AnyJson = {
  134. streetAddress: props.streetAddress,
  135. streetAddressSecond: props.streetAddressSecond,
  136. streetAddressThird: props.streetAddressThird,
  137. cp: props.postalCode,
  138. city: props.addressCity,
  139. }
  140. if (props.addressCountryId) {
  141. const country: Country = em.find(Country, props.addressCountryId)
  142. query['country'] = country?.name
  143. }
  144. const url = UrlUtils.addQuery(baseUrl, query)
  145. const responses: CollectionResponsePromise = await apiRequestService.get(url)
  146. if (responses['member'].length > 0) {
  147. onPositionUpdate(
  148. new LatLng(
  149. responses['member'][0]['latitude'],
  150. responses['member'][0]['longitude'],
  151. ),
  152. )
  153. if (responses['member'].length > 1) {
  154. zoom.value = 6
  155. gpsResponses.value = responses['member']
  156. } else {
  157. zoom.value = 12
  158. }
  159. }
  160. }
  161. const addressChoice = (key: number): void => {
  162. zoom.value = 12
  163. onPositionUpdate(
  164. new LatLng(
  165. gpsResponses.value[key]['latitude'] as number,
  166. gpsResponses.value[key]['longitude'] as number,
  167. ),
  168. )
  169. }
  170. </script>
  171. <style scoped lang="scss">
  172. .address_choices {
  173. cursor: pointer;
  174. width: 60%;
  175. display: flex;
  176. justify-content: space-between;
  177. align-items: center;
  178. padding: 0.75rem 1rem;
  179. border-radius: 0.5rem;
  180. margin-top: 0.5rem;
  181. background-color: #f9f9f9;
  182. transition: background-color 0.2s ease;
  183. &:hover {
  184. background-color: #eef3ff;
  185. }
  186. .v-btn {
  187. flex-shrink: 0;
  188. }
  189. }
  190. :deep(.v-skeleton-loader__image) {
  191. height: 350px;
  192. }
  193. :deep(.map_wrap) {
  194. height: 350px;
  195. }
  196. </style>