@dative-gpi/foundation-shared-components 1.0.137-maps4 → 1.0.137-reportV2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -50,7 +50,6 @@
50
50
  v-if="$props.description"
51
51
  class="fs-checkbox-description"
52
52
  font="text-overline"
53
- :lineClamp="2"
54
53
  :style="style"
55
54
  >
56
55
  {{ $props.description }}
@@ -50,7 +50,6 @@
50
50
  v-if="$props.description"
51
51
  class="fs-radio-description"
52
52
  font="text-overline"
53
- :lineClamp="2"
54
53
  :style="style"
55
54
  >
56
55
  {{ $props.description }}
@@ -35,7 +35,7 @@
35
35
  :elevation="0"
36
36
  :tickSize="4"
37
37
  :modelValue="$props.modelValue ?? undefined"
38
- @update:modelValue="$emit('update:modelValue', $event)"
38
+ @update:modelValue="(value) => $emit('update:modelValue', value)"
39
39
  v-bind="$attrs"
40
40
  >
41
41
  <template
@@ -55,7 +55,6 @@
55
55
  v-if="$props.description"
56
56
  class="fs-slider-description"
57
57
  font="text-overline"
58
- :lineClamp="2"
59
58
  :style="style"
60
59
  >
61
60
  {{ $props.description }}
@@ -1,25 +1,29 @@
1
1
  <template>
2
- <FSCol
2
+ <FSRow
3
+ width="hug"
4
+ align="top-left"
5
+ gap="16px"
3
6
  padding="8px 0px"
7
+ :wrap="false"
4
8
  >
5
- <FSRow
6
- gap="16px"
7
- :wrap="false"
8
- >
9
- <v-switch
10
- v-if="variant == 'left'"
11
- class="fs-switch"
12
- hide-details
13
- inset
14
- :validateOn="validateOn"
15
- :rules="$props.rules"
16
- :ripple="false"
17
- :style="style"
18
- :modelValue="$props.modelValue"
19
- @update:modelValue="onToggle"
20
- v-bind="$attrs"
21
- />
22
- <FSCol>
9
+ <v-switch
10
+ v-if="variant == 'left'"
11
+ class="fs-switch"
12
+ hide-details
13
+ inset
14
+ :validateOn="validateOn"
15
+ :rules="$props.rules"
16
+ :ripple="false"
17
+ :style="style"
18
+ :modelValue="$props.modelValue"
19
+ @update:modelValue="onToggle"
20
+ v-bind="$attrs"
21
+ />
22
+ <slot>
23
+ <FSCol
24
+ width="hug"
25
+ v-if="$props.label || $props.description || $slots.description"
26
+ >
23
27
  <FSSpan
24
28
  v-if="$props.label"
25
29
  class="fs-switch-label"
@@ -37,31 +41,33 @@
37
41
  class="fs-switch-description"
38
42
  font="text-overline"
39
43
  :style="style"
40
- :lineClamp="2"
41
44
  >
42
45
  {{ $props.description }}
43
46
  </FSSpan>
44
47
  </slot>
45
- </FSCol>
46
- <FSRow
47
- v-if="variant == 'right'"
48
- align="center-right"
49
- >
50
- <v-switch
51
- class="fs-switch"
52
- hide-details
53
- inset
54
- :validateOn="validateOn"
55
- :rules="$props.rules"
56
- :ripple="false"
57
- :style="style"
58
- :modelValue="$props.modelValue"
59
- @update:modelValue="onToggle"
60
- v-bind="$attrs"
48
+ <slot
49
+ name="footer"
61
50
  />
62
- </FSRow>
51
+ </FSCol>
52
+ </slot>
53
+ <FSRow
54
+ v-if="variant == 'right'"
55
+ align="center-right"
56
+ >
57
+ <v-switch
58
+ class="fs-switch"
59
+ hide-details
60
+ inset
61
+ :validateOn="validateOn"
62
+ :rules="$props.rules"
63
+ :ripple="false"
64
+ :style="style"
65
+ :modelValue="$props.modelValue"
66
+ @update:modelValue="onToggle"
67
+ v-bind="$attrs"
68
+ />
63
69
  </FSRow>
64
- </FSCol>
70
+ </FSRow>
65
71
  </template>
66
72
 
67
73
  <script lang="ts">
@@ -54,7 +54,6 @@
54
54
  v-if="$props.description"
55
55
  class="fs-base-field-description"
56
56
  font="text-overline"
57
- :lineClamp="2"
58
57
  >
59
58
  {{ $props.description }}
60
59
  </FSSpan>
@@ -215,8 +215,7 @@
215
215
  <FSSpan
216
216
  v-if="!readonly && $props.description"
217
217
  class="fs-rich-text-field-description"
218
- font="text-overline"
219
- :lineClamp="2"
218
+ font="text-underline"
220
219
  :style="style"
221
220
  >
222
221
  {{ $props.description }}
@@ -14,7 +14,7 @@
14
14
  v-if="map"
15
15
  >
16
16
  <FSMapTileLayer
17
- :layers="actualLayer"
17
+ :layer="actualLayer"
18
18
  />
19
19
  <FSMapMarker
20
20
  v-if="gpsPosition"
@@ -22,7 +22,38 @@
22
22
  :color="ColorEnum.Primary"
23
23
  :latlng="gpsPosition"
24
24
  />
25
- <slot />
25
+
26
+ <FSMapFeatureGroup
27
+ v-if="$props.areas"
28
+ :expected-layers="$props.areas.length"
29
+ @update:bounds="(bounds) => areaGroupBounds = bounds"
30
+ >
31
+ <FSMapPolygon
32
+ v-for="area in areas"
33
+ :key="area.id"
34
+ :color="area.color"
35
+ :latlngs="area.coordinates.map((coord) => ({lat: coord.latitude, lng: coord.longitude}))"
36
+ @click="$emit('update:selectedAreaId', area.id)"
37
+ />
38
+ </FSMapFeatureGroup>
39
+
40
+ <FSMapMarkerClusterGroup
41
+ v-if="$props.locations"
42
+ :expected-layers="$props.locations.length"
43
+ :disableClusteringAtZoom="defaultZoom"
44
+ @update:bounds="(bounds) => locationGroupBounds = bounds"
45
+ >
46
+ <FSMapMarker
47
+ v-for="location in $props.locations"
48
+ :selected="location.id === $props.selectedLocationId"
49
+ :key="location.id"
50
+ :label="location.label"
51
+ :color="location.color ?? ColorEnum.Primary"
52
+ :icon="location.icon ?? 'mdi-map-marker'"
53
+ :latlng="{lat: location.address.latitude, lng: location.address.longitude}"
54
+ @click="$emit('update:selectedLocationId', location.id)"
55
+ />
56
+ </FSMapMarkerClusterGroup>
26
57
  </template>
27
58
  </div>
28
59
 
@@ -69,7 +100,6 @@
69
100
  </FSCol>
70
101
 
71
102
  <FSMapOverlay
72
- v-if="overlaySlots && Object.keys(overlaySlots).length"
73
103
  :mode="$props.overlayMode"
74
104
  @update:mode="$emit('update:overlayMode', $event)"
75
105
  @update:height="(height) => overlayHeight = height"
@@ -95,9 +125,10 @@ import type {} from "leaflet.markercluster";
95
125
  import { map as createMap, control, tileLayer, latLngBounds, latLng, type LatLng, type FitBoundsOptions, type ZoomPanOptions, type LatLngBounds } from "leaflet";
96
126
 
97
127
  import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
128
+ import { type FSArea } from '@dative-gpi/foundation-shared-domain/models';
98
129
 
99
130
  import { useBreakpoints, useColors, useSlots } from "../../composables";
100
- import { ColorEnum, MapLayers, MapOverlayPositions, type MapLayer } from "../../models";
131
+ import { ColorEnum, type FSLocation, type MapLayer } from "../../models";
101
132
 
102
133
  import FSMapLayerButton from "./FSMapLayerButton.vue";
103
134
  import FSMapOverlay from "./FSMapOverlay.vue";
@@ -107,12 +138,19 @@ import FSCol from "../FSCol.vue";
107
138
 
108
139
  import FSMapMarker from "./FSMapMarker.vue";
109
140
  import FSMapTileLayer from "./FSMapTileLayer.vue";
141
+ import FSMapFeatureGroup from "./FSMapFeatureGroup.vue";
142
+ import FSMapMarkerClusterGroup from "./FSMapMarkerClusterGroup.vue";
143
+ import FSMapPolygon from "./FSMapPolygon.vue";
110
144
 
111
145
  export default defineComponent({
112
146
  name: "FSMap",
113
147
  components: {
114
148
  FSMapMarker,
115
149
  FSMapTileLayer,
150
+ FSMapFeatureGroup,
151
+ FSMapMarkerClusterGroup,
152
+ FSMapPolygon,
153
+
116
154
  FSMapLayerButton,
117
155
  FSMapOverlay,
118
156
  FSButton,
@@ -136,9 +174,9 @@ export default defineComponent({
136
174
  default: false
137
175
  },
138
176
  overlayMode: {
139
- type: String as PropType<MapOverlayPositions>,
177
+ type: String as PropType<'collapse' | 'half' | 'expand'>,
140
178
  required: false,
141
- default: MapOverlayPositions.Collapse
179
+ default: 'collapse'
142
180
  },
143
181
  showMyLocation: {
144
182
  type: Boolean,
@@ -156,29 +194,39 @@ export default defineComponent({
156
194
  default: false
157
195
  },
158
196
  center: {
159
- type: Array as PropType<number[] | null>,
197
+ type: Array as PropType<number[]>,
160
198
  required: false,
161
- default: null
199
+ default: () => [45.71, 5.07]
162
200
  },
163
- bounds: {
164
- type: Object as PropType<LatLngBounds | null>,
201
+ locations: {
202
+ type: Array as PropType<FSLocation[]>,
165
203
  required: false,
166
- default: null
204
+ default: () => [],
205
+ },
206
+ areas: {
207
+ type: Array as PropType<FSArea[]>,
208
+ required: false,
209
+ default: () => [],
167
210
  },
168
211
  currentLayer: {
169
- type: String as PropType<MapLayers>,
212
+ type: String as PropType<"map" | "imagery">,
170
213
  required: false,
171
- default: MapLayers.Map
214
+ default: "map"
172
215
  },
173
216
  allowedLayers: {
174
- type: Array as PropType<MapLayers[]>,
217
+ type: Array as PropType<string[]>,
175
218
  required: false,
176
- default: () => [MapLayers.Map, MapLayers.Imagery]
219
+ default: () => ["map", "imagery"]
177
220
  },
178
- dirtyZoom: {
179
- type: Number,
221
+ selectedLocationId: {
222
+ type: String as PropType<string | null>,
180
223
  required: false,
181
- default: 16
224
+ default: null
225
+ },
226
+ selectedAreaId: {
227
+ type: String as PropType<string | null>,
228
+ required: false,
229
+ default: null
182
230
  }
183
231
  },
184
232
  emits: ["update:modelValue", "update:selectedLocationId", "update:selectedAreaId", 'update:overlayMode', 'update:currentLayer', "click:latlng"],
@@ -198,7 +246,7 @@ export default defineComponent({
198
246
 
199
247
  provide('map', map);
200
248
 
201
- const defaultZoom = ref(props.dirtyZoom);
249
+ const defaultZoom = 16;
202
250
  const mapResizeObserver = new ResizeObserver(() => {
203
251
  if(!map.value) {
204
252
  return;
@@ -208,53 +256,29 @@ export default defineComponent({
208
256
 
209
257
  const mapLayers: MapLayer[] = [
210
258
  {
211
- name: MapLayers.Map,
259
+ name: "map",
212
260
  label: $tr("ui.map-layer.map", "Map"),
213
261
  image: new URL("../../assets/images/map/map.png", import.meta.url).href,
214
- layers: [
215
- tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
216
- maxZoom: 22,
217
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
218
- attribution: '© Google Map Data',
219
- className: 'fs-map-tile-base-layer'
220
- })
221
- ]
262
+ layer: tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
263
+ maxZoom: 22,
264
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
265
+ attribution: '© Google Map Data'
266
+ })
222
267
  },
223
268
  {
224
- name: MapLayers.Imagery,
269
+ name: "imagery",
225
270
  label: $tr("ui.map-layer.imagery", "Imagery"),
226
271
  image: new URL("../../assets/images/map/imagery.png", import.meta.url).href,
227
- layers: [
228
- tileLayer(`https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
229
- maxZoom: 22,
230
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
231
- attribution: '© Google Map Data',
232
- className: 'fs-map-tile-base-layer'
233
- })
234
- ]
235
- },
236
- {
237
- name: MapLayers.Snow,
238
- label: $tr("ui.map-layer.snow", "Snow ski map"),
239
- image: new URL("../../assets/images/map/snow.png", import.meta.url).href,
240
- layers: [
241
- tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
242
- maxZoom: 22,
243
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
244
- attribution: '© Google Map Data',
245
- className: 'fs-map-tile-base-layer fs-map-tile-grayscale-layer'
246
- }),
247
- tileLayer(`https://tiles.opensnowmap.org/pistes/{z}/{x}/{y}.png`, {
248
- maxZoom: 18,
249
- attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors & ODbL, &copy; <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
250
- className: 'fs-map-tile-base-layer'
251
- })
252
- ]
272
+ layer: tileLayer(`https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
273
+ maxZoom: 22,
274
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
275
+ attribution: '© Google Map Data'
276
+ })
253
277
  }
254
278
  ];
255
279
 
256
280
  const bottomOffset = computed(() => {
257
- if (props.overlayMode !== MapOverlayPositions.Expand && overlayHeight.value && isExtraSmall.value) {
281
+ if (props.overlayMode !== 'expand' && overlayHeight.value && isExtraSmall.value) {
258
282
  return overlayHeight.value;
259
283
  }
260
284
  return 0;
@@ -276,7 +300,20 @@ export default defineComponent({
276
300
  }));
277
301
 
278
302
  const actualLayer = computed(() => {
279
- return mapLayers.find((mapLayer) => mapLayer.name === props.currentLayer)?.layers ?? mapLayers[0].layers;
303
+ return mapLayers.find((layer) => layer.name === props.currentLayer)?.layer ?? mapLayers[0].layer;
304
+ });
305
+
306
+ const bounds = computed<LatLngBounds | null>(() => {
307
+ if(!locationGroupBounds.value && !areaGroupBounds.value) {
308
+ return null;
309
+ }
310
+ let bounds = locationGroupBounds.value;
311
+ if(bounds && areaGroupBounds.value) {
312
+ bounds.extend(areaGroupBounds.value);
313
+ } else if(areaGroupBounds.value) {
314
+ bounds = areaGroupBounds.value;
315
+ }
316
+ return bounds as LatLngBounds;
280
317
  });
281
318
 
282
319
  const overlaySlots = computed(() => {
@@ -295,7 +332,7 @@ export default defineComponent({
295
332
  return map.value.unproject(targetPoint, zoom);
296
333
  }
297
334
 
298
- const flyTo = (lat: number, lng: number, zoom: number = defaultZoom.value, options?: ZoomPanOptions) => {
335
+ const flyTo = (lat: number, lng: number, zoom: number = defaultZoom, options?: ZoomPanOptions) => {
299
336
  if(!map.value) {
300
337
  return;
301
338
  }
@@ -323,7 +360,7 @@ export default defineComponent({
323
360
  if(!map.value) {
324
361
  return;
325
362
  }
326
- map.value.setView(calculateTargetPosition(latLng(lat, lng), zoom), zoom);
363
+ map.value.setView(calculateTargetPosition(latLng(lat, lng)), zoom);
327
364
  }
328
365
 
329
366
  const fitBounds = (bounds: LatLngBounds, options?: FitBoundsOptions) => {
@@ -357,12 +394,11 @@ export default defineComponent({
357
394
  minZoom: 2,
358
395
  maxZoom: 22,
359
396
  maxBounds: latLngBounds(latLng(-90, -180), latLng(90, 180)),
360
- maxBoundsViscosity: 1.0,
361
- zoom: defaultZoom.value,
362
- center: props.center ? latLng(props.center[0], props.center[1]) : latLng(48.85782, 2.29521)
397
+ maxBoundsViscosity: 1.0
363
398
  };
364
399
 
365
400
  map.value = markRaw(createMap(leafletContainer.value, mapOptions));
401
+ setView(props.center[0], props.center[1], defaultZoom);
366
402
 
367
403
  map.value.on('click', (e: L.LeafletMouseEvent) => {
368
404
  emit('click:latlng', e.latlng);
@@ -392,27 +428,43 @@ export default defineComponent({
392
428
  mapResizeObserver.disconnect();
393
429
  });
394
430
 
395
- watch ([() => props.center, () => map.value], () => {
396
- if(!map.value || !props.center) {
431
+ watch (() => props.center, (center) => {
432
+ if(!map.value) {
397
433
  return;
398
434
  }
399
- setView(props.center[0], props.center[1], defaultZoom.value);
400
- }, { immediate: true });
435
+ setView(center[0], center[1], defaultZoom);
436
+ });
401
437
 
402
- watch([() => props.bounds, () => map.value], () => {
403
- if(!map.value || !props.bounds) {
438
+ watch (() => props.selectedLocationId, (selectedLocationId) => {
439
+ if(!map.value) {
404
440
  return;
405
441
  }
406
- fitBounds(props.bounds, { maxZoom: defaultZoom.value });
407
- });
442
+ const selectedLocation = props.locations.find((location) => location.id === selectedLocationId);
443
+ if(!selectedLocation) {
444
+ return;
445
+ }
446
+ flyTo(selectedLocation?.address.latitude, selectedLocation?.address.longitude, defaultZoom, { animate: false });
447
+ }, { immediate: true });
408
448
 
409
- watch(() => props.dirtyZoom, (newZoom) => {
410
- defaultZoom.value = newZoom;
411
- if(map.value) {
412
- map.value.setZoom(newZoom);
449
+ watch(() => props.selectedAreaId, (selectedAreaId) => {
450
+ if(!map.value) {
451
+ return;
452
+ }
453
+ const selectedArea = props.areas.find((area) => area.id === selectedAreaId);
454
+ if(!selectedArea) {
455
+ return;
413
456
  }
457
+ const bounds = latLngBounds(selectedArea.coordinates.map((coord) => latLng(coord.latitude, coord.longitude)));
458
+ fitBounds(bounds);
414
459
  }, { immediate: true });
415
460
 
461
+ watch( () => bounds.value, (bounds) => {
462
+ if(!map.value || !bounds) {
463
+ return;
464
+ }
465
+ fitBounds(bounds, { maxZoom: defaultZoom });
466
+ });
467
+
416
468
  return {
417
469
  ColorEnum,
418
470
  defaultZoom,
@@ -82,20 +82,20 @@ export default {
82
82
  iconSize: [size, size],
83
83
  iconAnchor: [size / 2, size / 2],
84
84
  });
85
- } else if(props.variant === 'location') {
85
+ } else if(props.variant === 'location' && props.icon) {
86
86
  const size = 36;
87
87
  icon = divIcon({
88
- html: locationMarkerHtml(props.icon ?? "mdi-map-marker", getColors(props.color).base, props.label),
88
+ html: locationMarkerHtml(props.icon, getColors(props.color).base, props.label),
89
89
  iconSize: [size, size],
90
- className: props.selected ? 'fs-map-marker fs-map-location fs-map-location-selected' : 'fs-map-marker fs-map-location',
90
+ className: props.selected ? 'fs-map-location fs-map-location-selected' : 'fs-map-location',
91
91
  iconAnchor: [size / 2, size / 2],
92
92
  });
93
93
  } else {
94
- const size = 16;
94
+ const size = 20;
95
95
  icon = divIcon({
96
- html: pinMarkerHtml(getColors(props.color).base, props.label),
96
+ html: pinMarkerHtml(getColors(props.color).base),
97
97
  iconSize: [size, size],
98
- className: props.selected ? 'fs-map-marker fs-map-pin fs-map-pin-selected' : 'fs-map-marker fs-map-pin',
98
+ className: props.selected ? 'fs-map-location fs-map-location-selected' : 'fs-map-location',
99
99
  iconAnchor: [size / 2, size / 2],
100
100
  });
101
101
  }
@@ -45,7 +45,7 @@ export default {
45
45
 
46
46
  return divIcon({
47
47
  html: clusterMarkerHtml(cluster.getChildCount()),
48
- className: 'fs-map-marker fs-map-cluster-marker',
48
+ className: 'fs-map-location fs-map-location-full',
49
49
  iconSize: [size, size],
50
50
  iconAnchor: [size / 2, size / 2],
51
51
  });
@@ -1,16 +1,16 @@
1
1
  <template>
2
2
  <v-overlay
3
3
  v-if="isExtraSmall"
4
- :modelValue="$props.mode === MapOverlayPositions.Expand"
4
+ :modelValue="$props.mode === 'expand'"
5
5
  :contained="true"
6
- @click="$emit('update:mode', MapOverlayPositions.Collapse)"
6
+ @click="$emit('update:mode', 'collapse')"
7
7
  zIndex="0"
8
8
  />
9
9
  <div
10
10
  v-show="isExtraSmall"
11
11
  ref="mobileOverlayWrapper"
12
12
  class="fs-map-overlay-mobile"
13
- :style="{ height: $props.mode === MapOverlayPositions.Expand ? '90%' : ($props.mode === MapOverlayPositions.Half ? '60%' : 'auto') }"
13
+ :style="{ height: $props.mode === 'expand' ? '90%' : ($props.mode === 'half' ? '60%' : 'auto') }"
14
14
  >
15
15
  <FSCard
16
16
  width="100%"
@@ -29,15 +29,15 @@
29
29
  @mousedown="onClick"
30
30
  >
31
31
  <FSIcon>
32
- {{ $props.mode === MapOverlayPositions.Expand ? 'mdi-chevron-down' : 'mdi-chevron-up' }}
32
+ {{ $props.mode === 'expand' ? 'mdi-chevron-down' : 'mdi-chevron-up' }}
33
33
  </FSIcon>
34
34
  </FSRow>
35
35
  <slot
36
- v-if="$props.mode === MapOverlayPositions.Collapse"
36
+ v-if="$props.mode === 'collapse'"
37
37
  name="collapsed"
38
38
  />
39
39
  <FSCol
40
- v-if="$props.mode !== MapOverlayPositions.Collapse"
40
+ v-if="$props.mode !== 'collapse'"
41
41
  height="fill"
42
42
  style="min-height: 0;"
43
43
  >
@@ -70,8 +70,6 @@ import { defineComponent, type PropType, onUnmounted, onMounted, ref } from "vue
70
70
 
71
71
  import { useBreakpoints } from "../../composables";
72
72
 
73
- import { MapOverlayPositions } from '@dative-gpi/foundation-shared-components/models';
74
-
75
73
  import FSCard from "../FSCard.vue";
76
74
  import FSIcon from "../FSIcon.vue";
77
75
  import FSCol from "../FSCol.vue";
@@ -87,9 +85,9 @@ export default defineComponent({
87
85
  },
88
86
  props: {
89
87
  mode: {
90
- type: String as PropType<MapOverlayPositions>,
88
+ type: String as PropType<"collapse" | "half" | "expand">,
91
89
  required: false,
92
- default: MapOverlayPositions.Collapse
90
+ default: "collapse"
93
91
  }
94
92
  },
95
93
  emits: ["update:mode", "update:height", "update:width"],
@@ -103,11 +101,11 @@ export default defineComponent({
103
101
  const desktopResizeObserver = ref<ResizeObserver | null>(null);
104
102
 
105
103
  const onClick = (): void => {
106
- if (props.mode === MapOverlayPositions.Expand) {
107
- emit("update:mode", MapOverlayPositions.Collapse);
104
+ if (props.mode === "expand") {
105
+ emit("update:mode", "collapse");
108
106
  return;
109
107
  }
110
- emit("update:mode", MapOverlayPositions.Expand);
108
+ emit("update:mode", "expand");
111
109
  }
112
110
 
113
111
  onMounted(() => {
@@ -146,7 +144,6 @@ export default defineComponent({
146
144
  return {
147
145
  mobileOverlayWrapper,
148
146
  isTouchScreenEnabled,
149
- MapOverlayPositions,
150
147
  desktopOverlay,
151
148
  isExtraSmall,
152
149
  onClick
@@ -12,15 +12,15 @@ import { MAP } from './keys';
12
12
  export default {
13
13
  name: 'FSMapTileLayer',
14
14
  props: {
15
- layers: {
16
- type: Object as PropType<Layer[]>,
15
+ layer: {
16
+ type: Object as PropType<Layer>,
17
17
  required: false
18
18
  }
19
19
  },
20
20
  setup(props) {
21
21
  const map = inject<Ref<Map | null>>(MAP);
22
22
 
23
- let lastLayers = props.layers;
23
+ const lastLayer = props.layer;
24
24
 
25
25
  if(!map) {
26
26
  throw new Error('FSMapTileLayer must be used inside a FSMap component');
@@ -31,26 +31,20 @@ export default {
31
31
  }
32
32
 
33
33
  const updateLayer = () => {
34
- if (!props.layers || !map.value) {
34
+ if (!props.layer || !map.value) {
35
35
  return;
36
36
  }
37
37
 
38
- if(lastLayers) {
39
- lastLayers.forEach(layer => {
40
- layer.removeFrom(map.value!);
41
- });
38
+ if(lastLayer) {
39
+ map.value.removeLayer(lastLayer);
42
40
  }
43
41
 
44
- lastLayers = [];
45
-
46
- props.layers.forEach(layer => {
47
- lastLayers?.push(layer.addTo(map.value!));
48
- });
42
+ props.layer.addTo(map.value);
49
43
  };
50
44
 
51
45
  onMounted(updateLayer);
52
46
 
53
- watch(() => props.layers, updateLayer);
47
+ watch(() => props.layer, updateLayer);
54
48
  }
55
49
  };
56
50
  </script>
@@ -97,13 +97,6 @@
97
97
  :imageId="$props.imageId"
98
98
  :width="imageSize"
99
99
  />
100
- <FSIconCard
101
- v-else-if="$props.icon"
102
- backgroundVariant="standard"
103
- :backgroundColor="ColorEnum.Background"
104
- :icon="$props.icon"
105
- :size="imageSize"
106
- />
107
100
  </FSRow>
108
101
  </FSCol>
109
102
  </FSTile>
@@ -115,7 +108,6 @@ import { computed, defineComponent, type PropType } from "vue";
115
108
  import { useBreakpoints } from "@dative-gpi/foundation-shared-components/composables";
116
109
  import { ColorEnum } from "@dative-gpi/foundation-shared-components/models";
117
110
 
118
- import FSIconCard from "../FSIconCard.vue";
119
111
  import FSImage from "../FSImage.vue";
120
112
  import FSColor from "../FSColor.vue";
121
113
  import FSSpan from "../FSSpan.vue";
@@ -126,7 +118,6 @@ import FSRow from "../FSRow.vue";
126
118
  export default defineComponent({
127
119
  name: "FSGroupTileUI",
128
120
  components: {
129
- FSIconCard,
130
121
  FSImage,
131
122
  FSColor,
132
123
  FSSpan,
@@ -140,10 +131,6 @@ export default defineComponent({
140
131
  required: false,
141
132
  default: null
142
133
  },
143
- icon: {
144
- type: String,
145
- required: false
146
- },
147
134
  label: {
148
135
  type: String as PropType<string | null>,
149
136
  required: false,
@@ -196,7 +183,7 @@ export default defineComponent({
196
183
  });
197
184
 
198
185
  const infoWidth = computed((): string => {
199
- if (!props.imageId && !props.icon) {
186
+ if (!props.imageId) {
200
187
  return "100%";
201
188
  }
202
189
  return `calc(100% - ${imageSize.value}px - 24px)`;
package/models/map.ts CHANGED
@@ -1,20 +1,18 @@
1
1
  import { type Layer } from "leaflet";
2
2
 
3
+ import { type Address } from "@dative-gpi/foundation-shared-domain/models";
4
+
3
5
  export interface MapLayer {
4
- name : MapLayers;
6
+ name : string;
5
7
  label: string;
6
8
  image: string;
7
- layers: Layer[];
8
- }
9
-
10
- export enum MapLayers {
11
- Map = "map",
12
- Imagery = "imagery",
13
- Snow= "snow"
9
+ layer: Layer;
14
10
  }
15
11
 
16
- export enum MapOverlayPositions {
17
- Expand = "expand",
18
- Half = "half",
19
- Collapse = "collapse",
12
+ export interface FSLocation {
13
+ id: string;
14
+ label: string;
15
+ icon: string | null;
16
+ address: Address;
17
+ color: string | null;
20
18
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dative-gpi/foundation-shared-components",
3
3
  "sideEffects": false,
4
- "version": "1.0.137-maps4",
4
+ "version": "1.0.137-reportV2",
5
5
  "description": "",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -10,8 +10,8 @@
10
10
  "author": "",
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
- "@dative-gpi/foundation-shared-domain": "1.0.137-maps4",
14
- "@dative-gpi/foundation-shared-services": "1.0.137-maps4"
13
+ "@dative-gpi/foundation-shared-domain": "1.0.137-reportV2",
14
+ "@dative-gpi/foundation-shared-services": "1.0.137-reportV2"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "@dative-gpi/bones-ui": "^1.0.0",
@@ -35,5 +35,5 @@
35
35
  "sass": "1.71.1",
36
36
  "sass-loader": "13.3.2"
37
37
  },
38
- "gitHead": "2ff4d8aa1783b9f845daae1e8e6c9b9d23820b72"
38
+ "gitHead": "57f88a90528a7849e930dbc8eea7223a84c58c9f"
39
39
  }
@@ -6,15 +6,7 @@
6
6
  .fs-leaflet-container {
7
7
  width: 100%;
8
8
  height: 100%;
9
- z-index: 0;
10
-
11
- .fs-map-tile-base-layer {
12
- filter: grayscale(var(--fs-map-container-grayscale));
13
- }
14
-
15
- .fs-map-tile-grayscale-layer {
16
- filter: grayscale(100%);
17
- }
9
+ filter: grayscale(var(--fs-map-container-grayscale));
18
10
  }
19
11
 
20
12
  .fs-map-overlay-mobile {
@@ -85,10 +77,17 @@
85
77
  }
86
78
  }
87
79
 
88
- .fs-map-marker > div {
80
+ .fs-map-point-pin {
81
+ background-color: var(--fs-map-point-pin-color);
82
+ border-radius: 100%;
83
+ }
84
+
85
+ .fs-map-location > div {
89
86
  display: flex;
90
87
  height: 100%;
88
+ color: var(--fs-map-location-pin-color);
91
89
  border-radius: 50%;
90
+ background-color: white;
92
91
  filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.4));
93
92
  align-items: center;
94
93
  justify-content: center;
@@ -104,7 +103,7 @@
104
103
  }
105
104
  }
106
105
 
107
- .fs-map-cluster-marker > div {
106
+ .fs-map-location-full > div {
108
107
  background-color: var(--fs-map-location-pin-color);
109
108
  color: white;
110
109
  }
@@ -127,41 +126,6 @@
127
126
  }
128
127
  }
129
128
 
130
- .fs-map-location > div {
131
- color: var(--fs-map-location-pin-color);
132
- background-color: white;
133
- }
134
-
135
- .fs-map-pin > div {
136
- background-color: var(--fs-map-point-pin-color);
137
- position: relative;
138
-
139
- transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
140
- }
141
-
142
- .fs-map-pin > div::before {
143
- content: "";
144
- position: absolute;
145
- top: -4px;
146
- left: -4px;
147
- width: calc(100% + 8px);
148
- height: calc(100% + 8px);
149
- border-radius: 50%;
150
- border: 2px solid var(--fs-map-point-pin-color);
151
- opacity: 0.4;
152
-
153
- @include clickscreen {
154
- &:hover {
155
- opacity: 1;
156
- }
157
- }
158
- }
159
-
160
- .fs-map-pin-selected > div {
161
- transform: scale(1.35);
162
- }
163
-
164
-
165
129
  .fs-map-site {
166
130
  opacity: 0.6;
167
131
  transition: opacity 0.28s cubic-bezier(0.4, 0, 0.2, 1);
@@ -55,7 +55,6 @@
55
55
  @import "fs_slide_group.scss";
56
56
  @import "fs_slider.scss";
57
57
  @import "fs_span.scss";
58
- @import "fs_status_rich_card.scss";
59
58
  @import "fs_switch.scss";
60
59
  @import "fs_tag.scss";
61
60
  @import "fs_tabs.scss";
package/tools/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./alertsTools";
2
2
  export * from "./chartsTools";
3
+ export * from "./reportsTools";
3
4
  export * from "./timeRangeTools";
@@ -0,0 +1,38 @@
1
+ import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
2
+ import { ColorEnum } from "../models";
3
+ import { JobState } from "@dative-gpi/foundation-shared-domain/enums";
4
+
5
+ const { $tr } = useTranslationsProvider();
6
+
7
+ export const getColorByState = (state: number | JobState | undefined) => {
8
+ switch (state) {
9
+ case JobState.Succeeded:
10
+ return ColorEnum.Success;
11
+ case JobState.Failed:
12
+ return ColorEnum.Error;
13
+ default:
14
+ return ColorEnum.Primary;
15
+ }
16
+ };
17
+
18
+ export const getIconByState = (state: number | JobState | undefined) => {
19
+ switch (state) {
20
+ case JobState.Succeeded:
21
+ return 'mdi-check-circle-outline';
22
+ case JobState.Failed:
23
+ return 'mdi-alert-circle-outline';
24
+ default:
25
+ return 'mdi-alert-circle-outline';
26
+ }
27
+ };
28
+
29
+ export const getLabelByState = (state: number | JobState | undefined) => {
30
+ switch (state) {
31
+ case JobState.Succeeded:
32
+ return $tr('ui.common.success', 'Success');
33
+ case JobState.Failed:
34
+ return $tr('ui.common.error', 'Error');
35
+ default:
36
+ return $tr('ui.common.executed', 'Executed');
37
+ }
38
+ };
Binary file
@@ -1,170 +0,0 @@
1
- <template>
2
- <component
3
- v-if="$props.modelStatus"
4
- class="fs-status-rich-card"
5
- variant="standard"
6
- :is="$attrs.onClick ? FSClickable : FSCard"
7
- :padding="$props.padding"
8
- :height="$props.height"
9
- :width="$props.width"
10
- :color="color"
11
- :style="style"
12
- >
13
- <FSCol
14
- align="center-center"
15
- :gap="$props.gap"
16
- >
17
- <FSRow
18
- align="top-center"
19
- >
20
- <FSIcon
21
- v-if="icon"
22
- >
23
- {{ icon }}
24
- </FSIcon>
25
- <FSText
26
- v-if="value"
27
- font="text-button"
28
- >
29
- {{ value }}
30
- </FSText>
31
- </FSRow>
32
- <FSText
33
- font="text-overline"
34
- align="center"
35
- :lineClamp="$props.titleClamp"
36
- >
37
- {{ title }}
38
- </FSText>
39
- <slot
40
- name="footer"
41
- v-bind="{ color }"
42
- />
43
- </FSCol>
44
- <div
45
- class="fs-status-rich-card-corner"
46
- >
47
- <slot
48
- name="corner"
49
- v-bind="{ color }"
50
- />
51
- </div>
52
- </component>
53
- </template>
54
-
55
- <script lang="ts">
56
- import { computed, defineComponent, type PropType, type StyleValue } from "vue";
57
-
58
- import { ColorEnum, type FSDeviceStatusGroup, type FSModelStatus } from "@dative-gpi/foundation-shared-components/models";
59
- import { useColors } from "@dative-gpi/foundation-shared-components/composables";
60
-
61
- import FSClickable from "../FSClickable.vue";
62
- import FSCard from "../FSCard.vue";
63
- import FSIcon from "../FSChip.vue";
64
- import FSText from "../FSText.vue";
65
- import FSCol from "../FSCol.vue";
66
-
67
- export default defineComponent({
68
- name: "FSStatusRichCard",
69
- components: {
70
- FSClickable,
71
- FSCard,
72
- FSIcon,
73
- FSText,
74
- FSCol
75
- },
76
- props: {
77
- height: {
78
- type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
79
- required: false,
80
- default: "100px"
81
- },
82
- width: {
83
- type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
84
- required: false,
85
- default: "160px"
86
- },
87
- padding: {
88
- type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
89
- required: false,
90
- default: "12px"
91
- },
92
- gap: {
93
- type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
94
- required: false,
95
- default: "8px"
96
- },
97
- title: {
98
- type: [String, null] as PropType<string | null>,
99
- required: false,
100
- default: null
101
- },
102
- titleClamp: {
103
- type: Number,
104
- required: false,
105
- default: 2
106
- },
107
- color: {
108
- type: [String, null] as PropType<string | null>,
109
- required: false,
110
- default: null
111
- },
112
- fillBackground: {
113
- type: Boolean,
114
- required: false,
115
- default: false
116
- },
117
- modelStatus: {
118
- type: Object as PropType<FSModelStatus | undefined>,
119
- required: true
120
- },
121
- statusGroup: {
122
- type: Object as PropType<FSDeviceStatusGroup | undefined>,
123
- required: true
124
- }
125
- },
126
- setup(props) {
127
- const { getColors } = useColors();
128
-
129
- const color = computed((): string => {
130
- return props.color ?? props.statusGroup?.color ?? props.modelStatus?.colorDefault ?? ColorEnum.Primary;
131
- });
132
-
133
- const icon = computed((): string | null => {
134
- return props.statusGroup?.icon ?? props.modelStatus?.iconDefault ?? null;
135
- });
136
-
137
- const title = computed((): string => {
138
- return props.title ?? props.modelStatus?.label;
139
- });
140
-
141
- const value = computed((): string | null => {
142
- if (props.statusGroup?.label) {
143
- return props.statusGroup.label;
144
- }
145
- if (props.statusGroup?.value && !isNaN(parseFloat(props.statusGroup?.value))) {
146
- return `${parseFloat(props.statusGroup.value).toLocaleString("fullwide", { maximumFractionDigits: 2 })} ${props.statusGroup.unit}`;
147
- }
148
- if (props.statusGroup?.value) {
149
- return `${props.statusGroup?.value} ${props.statusGroup?.unit}`;
150
- }
151
- return null;
152
- });
153
-
154
- const style = computed((): StyleValue => ({
155
- "--fs-status-rich-card-background-color": props.fillBackground ? getColors(color.value).light : "transparent",
156
- "--fs-status-rich-card-border-color": props.fillBackground ? getColors(color.value).base : getColors(ColorEnum.Light).dark
157
- }));
158
-
159
- return {
160
- FSClickable,
161
- FSCard,
162
- color,
163
- style,
164
- title,
165
- value,
166
- icon
167
- };
168
- }
169
- });
170
- </script>
@@ -1,68 +0,0 @@
1
- <template>
2
- <FSSelectField
3
- :items="items"
4
- :modelValue="$props.modelValue"
5
- @update:modelValue="$emit('update:modelValue', $event)"
6
- v-bind="$attrs"
7
- >
8
- <template
9
- #item-prepend="{ item }"
10
- >
11
- <FSIcon
12
- :icon="item.icon"
13
- />
14
- </template>
15
- </FSSelectField>
16
- </template>
17
-
18
- <script lang="ts">
19
- import type { PropType} from "vue";
20
- import { defineComponent } from "vue";
21
-
22
- import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
23
-
24
- import { MapLayers } from '@dative-gpi/foundation-shared-components/models';
25
-
26
- import FSSelectField from "../fields/FSSelectField.vue";
27
- import FSIcon from '@dative-gpi/foundation-shared-components/components/FSIcon.vue';
28
-
29
- export default defineComponent({
30
- name: "FSSelectMapLayer",
31
- components: {
32
- FSIcon,
33
- FSSelectField
34
- },
35
- props: {
36
- modelValue: {
37
- type: [String, Array] as PropType<MapLayers | MapLayers[]>,
38
- required: false
39
- }
40
- },
41
- emits: ["update:modelValue"],
42
- setup() {
43
- const { $tr } = useTranslationsProvider();
44
-
45
- const items = [
46
- {
47
- id: MapLayers.Map,
48
- icon: 'mdi-map',
49
- label: $tr("ui.map-layer.map", "Map")
50
- },
51
- {
52
- id: MapLayers.Imagery,
53
- icon: 'mdi-satellite',
54
- label: $tr("ui.map-layer.imagery", "Imagery")
55
- },
56
- {
57
- id: MapLayers.Snow,
58
- icon: 'mdi-snowflake',
59
- label: $tr("ui.map-layer.snow", "Snow ski map")
60
- }
61
- ]
62
-
63
- return {
64
- items
65
- };
66
- }
67
- });
68
- </script>
@@ -1,13 +0,0 @@
1
- .fs-status-rich-card {
2
- position: relative;
3
- flex-grow: 1;
4
- background-color: var(--fs-status-rich-card-background-color);
5
- border-color: var(--fs-status-rich-card-border-color);
6
- }
7
-
8
- .fs-status-rich-card-corner {
9
- position: absolute;
10
- display: flex;
11
- right: 2px;
12
- top: 2px;
13
- }