Repository: Huor97/faverton-app
Pull request #46: feat(74): simulation - can start draw with btn
Author: login: Huor97
Created: 2025-04-28T16:07:58Z | Merged: 2025-04-28T16:49:54Z

This pull request introduces a feature to support drawing and managing surface areas on a map using Leaflet, along with integration into the simulation parameters UI. The key changes include adding a new `mapStore` for state management, enhancing the map component with drawing functionalities, and updating the simulation parameters component to reflect drawn areas.

### Map Drawing Enhancements:
* Added `drawControl` and `drawnItems` to the `FavertonMap.client.vue` component to enable drawing polygons on the map. Integrated event handlers for `draw:created`, `draw:deleted`, and `draw:editstop` to calculate and manage the drawn area's surface. [[1]](diffhunk://#diff-65f6c4414859c02ccd65cf9f320f77e2417ae20effd3b6570364041e3bc4c432L5-R17) [[2]](diffhunk://#diff-65f6c4414859c02ccd65cf9f320f77e2417ae20effd3b6570364041e3bc4c432L52-R63) [[3]](diffhunk://#diff-65f6c4414859c02ccd65cf9f320f77e2417ae20effd3b6570364041e3bc4c432L83-R122)
* Introduced a `watch` function to trigger polygon drawing when `activateDrawing` is set in the `mapStore`.

### State Management:
* Created a new `mapStore` in `faverton-nuxt3/app/stores/map.ts` to manage drawing state (`activateDrawing`) and the calculated `drawnArea`. Added actions to start drawing, reset drawing, and update the drawn area.

### Simulation Parameters Integration:
* Updated the `SimulationParameters.vue` component to watch the `drawnArea` from `mapStore` and update the `surface` value accordingly. Added a button to activate drawing and a divider for alternative input methods. [[1]](diffhunk://#diff-7afdc2ef547430fdff993e6ab2a3b48b1e2efc4437058324d438918560bc6e70R3-R7) [[2]](diffhunk://#diff-7afdc2ef547430fdff993e6ab2a3b48b1e2efc4437058324d438918560bc6e70R54-R66) [[3]](diffhunk://#diff-7afdc2ef547430fdff993e6ab2a3b48b1e2efc4437058324d438918560bc6e70R96-R100)

### Type Definitions:
* Added a new `DrawControlOptions` interface in `leaflet-map.ts` to extend Leaflet's drawing options with type support for polygons and other shapes.


Diff:
diff --git a/faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue b/faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue
index 306d1b7..c690e48 100644
--- a/faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue
+++ b/faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue
@@ -2,15 +2,19 @@
 import L from 'leaflet';
 import 'leaflet-draw/dist/leaflet.draw.css';
 import 'leaflet-draw';
-import type { LatLngExpression } from 'leaflet';
+import type { DrawMap, LatLngExpression } from 'leaflet';
 import type { FeatureCollection } from "~~/shared/types/address/new-base-address-national";
 
 const addressStore = useAddressStore();
+const mapStore = useMapStore();
+
 const featureCollection = computed<FeatureCollection | null>(() => {
   return addressStore.savedAddress?.featureCollection || null;
 });
 
 let leafletMap: L.Map | null = null;
+let drawControl: L.Control.Draw | null = null;
+let drawnItems: L.FeatureGroup | null = null;
 const isMapReady = ref(false);
 
 const getCoordinates = (address: FeatureCollection): LatLngExpression | null => {
@@ -49,16 +53,14 @@ watch(() => featureCollection.value, (newAddress) => {
   }
 }, { immediate: true });
 
-// Draw and calculate surface area
 const onMapReady = (mapInstance: L.Map) => {
   leafletMap = mapInstance;
   isMapReady.value = true;
 
   if (import.meta.client) {
-    const drawnItems = new L.FeatureGroup();
+    drawnItems = new L.FeatureGroup();
     mapInstance.addLayer(drawnItems);
-
-    const drawControl = new L.Control.Draw({
+    drawControl = new L.Control.Draw({
       position: `topright`,
       draw: {
         polygon: {
@@ -80,18 +82,48 @@ const onMapReady = (mapInstance: L.Map) => {
         featureGroup: drawnItems,
       },
     });
-
     mapInstance.addControl(drawControl);
     // @ts-expect-error Leaflet Draw event type mismatch with LeafletEventHandlerFn
     mapInstance.on(`draw:created`, (e: L.DrawEvents.Created) => {
+      // Clear all existing layers to enforce a single-polygon constraint.
+      drawnItems?.clearLayers();
       const layer = e.layer;
-      drawnItems.addLayer(layer);
+      drawnItems?.addLayer(layer);
       // @ts-expect-error Leaflet Draw layer type is not properly typed for getLatLngs
       const area = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
-      layer.bindTooltip(`Superficie: ${area.toFixed(2)} m²`, { permanent: true, direction: `center` });
+      const roundedArea = Math.round(area);
+      mapStore.setDrawnArea(roundedArea);
+      layer.bindTooltip(`Superficie: ${roundedArea} m²`, { permanent: true, direction: `center` });
+    });
+
+    mapInstance.on(`draw:deleted`, () => {
+      mapStore.setDrawnArea(0);
+    });
+
+    mapInstance.on(`draw:editstop`, () => {
+      if (drawnItems?.getLayers().length === 0) {
+        mapStore.setDrawnArea(0);
+      }
+      else {
+        const layer = drawnItems?.getLayers()[0];
+        // @ts-expect-error Leaflet Draw layer type
+        const area = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
+        const roundedArea = Math.round(area);
+        mapStore.setDrawnArea(roundedArea);
+      }
     });
   }
 };
+
+watch(() => mapStore.activateDrawing, (activate) => {
+  if (activate && leafletMap && drawControl) {
+    const drawOptions = (drawControl.options as DrawControlOptions).draw || {};
+    const polygonOptions = drawOptions.polygon || { allowIntersection: true, showArea: true };
+    drawOptions.polygon = polygonOptions;
+    new L.Draw.Polygon(leafletMap as DrawMap, polygonOptions).enable();
+    mapStore.resetDrawing();
+  }
+}, { immediate: true });
 </script>
 
 <template>
diff --git a/faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue b/faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue
index 09483ca..eedbe3a 100644
--- a/faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue
+++ b/faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue
@@ -1,7 +1,10 @@
 <script setup lang="ts">
 import { useAddSimulation } from '~/composables/useSimulation';
+import { useMapStore } from '~/stores/map';
 import type { Properties } from '~~/shared/types/address/new-base-address-national';
 
+const mapStore = useMapStore();
+
 const props = defineProps<{
   addressProperty: Properties | null
   responseSolarEnergyId: SolarEnergyResponse | null
@@ -48,6 +51,19 @@ const info = [
     infoBol: `3`,
   },
 ];
+
+watch(() => mapStore.drawnArea, (newArea) => {
+  if (newArea > 0) {
+    surface.value = newArea;
+  }
+  else if (newArea === 0) {
+    surface.value = 1;
+  }
+});
+
+const activateDrawing = () => {
+  mapStore.startDrawing();
+};
 </script>
 
 <template>
@@ -76,7 +92,12 @@ const info = [
       <div class="flex gap-2">
         <UButton
           icon="i-heroicons-paint-brush-20-solid"
-          label="Desiner votre surface"
+          label="Dessiner votre surface"
+          @click="activateDrawing"
+        />
+        <UDivider
+          label="OU"
+          orientation="vertical"
         />
         <FavertonInputSurface v-model="surface" />
       </div>
diff --git a/faverton-nuxt3/app/stores/map.ts b/faverton-nuxt3/app/stores/map.ts
new file mode 100644
index 0000000..fc710f8
--- /dev/null
+++ b/faverton-nuxt3/app/stores/map.ts
@@ -0,0 +1,17 @@
+export const useMapStore = defineStore(`map`, {
+  state: () => ({
+    activateDrawing: false,
+    drawnArea: 0,
+  }),
+  actions: {
+    startDrawing() {
+      this.activateDrawing = true;
+    },
+    setDrawnArea(area: number) {
+      this.drawnArea = area;
+    },
+    resetDrawing() {
+      this.activateDrawing = false;
+    },
+  },
+});
diff --git a/faverton-nuxt3/shared/types/leaflet-map.ts b/faverton-nuxt3/shared/types/leaflet-map.ts
new file mode 100644
index 0000000..ed72e6d
--- /dev/null
+++ b/faverton-nuxt3/shared/types/leaflet-map.ts
@@ -0,0 +1,12 @@
+import type L from 'leaflet';
+
+export interface DrawControlOptions extends L.Control.DrawOptions {
+  draw: {
+    polygon?: L.DrawOptions.PolygonOptions
+    polyline?: boolean
+    rectangle?: boolean
+    circle?: boolean
+    marker?: boolean
+    circlemarker?: boolean
+  }
+}


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces drawing functionality on the Leaflet map with state management and integration into the simulation parameters UI.  
- Enables polygon drawing with real-time surface area calculation on the map.  
- Adds a new map store to manage drawing state and integrates drawn areas into simulation parameters.  
- Extends Leaflet types for enhanced drawing configuration.

### Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

| File | Description |
| ---- | ----------- |
| faverton-nuxt3/shared/types/leaflet-map.ts | Adds DrawControlOptions interface to type Leaflet drawing options. |
| faverton-nuxt3/app/stores/map.ts | Creates a new map store for drawing state management. |
| faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue | Integrates drawing state into simulation parameters UI with a new drawing activation button. |
| faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue | Enhances the map component with drawing controls and handles created, deleted, and edited drawing events. |


<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue:94**
* The label 'Desiner votre surface' appears to have a typo; consider changing it to 'Dessiner votre surface'.
```
label="Desiner votre surface"
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-04-28T16:34:06Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces map drawing functionality integrated with simulation parameters for better surface area management. Key changes include:
- Adding a new mapStore to manage drawing state and calculated areas.
- Enhancing the map component (FavertonMap.client.vue) with Leaflet Draw event handlers.
- Integrating drawn area updates into the simulation parameters UI and fixing a label typo.

### Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

| File | Description |
| ---- | ----------- |
| faverton-nuxt3/shared/types/leaflet-map.ts | Added a new DrawControlOptions interface to support extended drawing options. |
| faverton-nuxt3/app/stores/map.ts | Introduced mapStore with actions to manage drawing state and area calculation. |
| faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue | Integrated drawn area updates with a watch function and fixed the button label typo. |
| faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue | Enhanced map component with drawing event handlers and polygon area calculations. |


<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**faverton-nuxt3/app/components/simulation/parameters/SimulationParameters.vue:59**
* Mapping a drawnArea of 0 to a surface value of 1 may be counterintuitive if 0 is a valid measurement; consider reviewing this fallback logic to ensure it aligns with the intended behavior.
```
else if (newArea === 0) { surface.value = 1; }
```
</details>


state: COMMENTED
author_association: NONE
submitted_at: 2025-04-28T16:45:10Z

Review comments:
diff_hunk: @@ -80,18 +82,44 @@ const onMapReady = (mapInstance: L.Map) => {
         featureGroup: drawnItems,
       },
     });
-
     mapInstance.addControl(drawControl);
     // @ts-expect-error Leaflet Draw event type mismatch with LeafletEventHandlerFn
     mapInstance.on(`draw:created`, (e: L.DrawEvents.Created) => {
+      drawnItems?.clearLayers();
       const layer = e.layer;
-      drawnItems.addLayer(layer);
+      drawnItems?.addLayer(layer);
       // @ts-expect-error Leaflet Draw layer type is not properly typed for getLatLngs
       const area = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
-      layer.bindTooltip(`Superficie: ${area.toFixed(2)} m²`, { permanent: true, direction: `center` });
+      const roundedArea = Math.round(area);
+      mapStore.setDrawnArea(roundedArea);
+      layer.bindTooltip(`Superficie: ${roundedArea} m²`, { permanent: true, direction: `center` });
+    });
+
+    mapInstance.on(`draw:deleted`, () => {
+      mapStore.setDrawnArea(0);
+    });
+
+    mapInstance.on(`draw:editstop`, () => {
+      if (drawnItems?.getLayers().length === 0) {
+        mapStore.setDrawnArea(0);
+      }
+      else {
+        const layer = drawnItems?.getLayers()[0];
+        // @ts-expect-error Leaflet Draw layer type
+        const area = L.GeometryUtil.geodesicArea(layer.getLatLngs()[0]);
+        const roundedArea = Math.round(area);
+        mapStore.setDrawnArea(roundedArea);
+      }
     });
   }
 };
+
+watch(() => mapStore.activateDrawing, (activate) => {
+  if (activate && leafletMap && drawControl) {
+    new L.Draw.Polygon(leafletMap as DrawMap, (drawControl.options as DrawControlOptions).draw.polygon).enable();
path: faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue
user: login: Copilot
body: The cast to DrawControlOptions assumes that draw.polygon is defined. Adding a check for its existence or providing a default configuration would improve robustness and prevent potential runtime errors.
```suggestion
    const drawOptions = (drawControl.options as DrawControlOptions).draw || {};
    const polygonOptions = drawOptions.polygon || { allowIntersection: true, showArea: true };
    drawOptions.polygon = polygonOptions; // Ensure polygon options are set
    new L.Draw.Polygon(leafletMap as DrawMap, polygonOptions).enable();
```
created_at: 2025-04-28T16:34:05Z
author_association: NONE
diff_hunk: @@ -80,18 +82,47 @@ const onMapReady = (mapInstance: L.Map) => {
         featureGroup: drawnItems,
       },
     });
-
     mapInstance.addControl(drawControl);
     // @ts-expect-error Leaflet Draw event type mismatch with LeafletEventHandlerFn
     mapInstance.on(`draw:created`, (e: L.DrawEvents.Created) => {
path: faverton-nuxt3/app/components/faverton/map/FavertonMap.client.vue
user: login: Copilot
body: [nitpick] Automatically clearing all drawn layers on each 'draw:created' event enforces a single drawn polygon. If this behavior is intentional, consider adding a comment to clarify this design choice for future maintainers.
```suggestion
    mapInstance.on(`draw:created`, (e: L.DrawEvents.Created) => {
      // Clear all existing layers to enforce a single-polygon constraint.
```
created_at: 2025-04-28T16:45:10Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-04-28T16:08:03Z
body: [vc]: #+jocxtP1i8mQ6X+4QQSTj1VogolyllVe4mPO6hmHd5w=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJmYXZlcnRvbi1hcHAiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vcm91aHMtcHJvamVjdHMvZmF2ZXJ0b24tYXBwLzZGRkhqWUs5cTFyNDdaUndmdkNwUTh4TU1Hb1AiLCJwcmV2aWV3VXJsIjoiZmF2ZXJ0b24tYXBwLWdpdC1mZWF0LTc0ZHJvdy1vdXItY2hvaXMtc3VyZmFjZS1yb3Vocy1wcm9qZWN0cy52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImZhdmVydG9uLWFwcC1naXQtZmVhdC03NGRyb3ctb3VyLWNob2lzLXN1cmZhY2Utcm91aHMtcHJvamVjdHMudmVyY2VsLmFwcCJ9LCJyb290RGlyZWN0b3J5IjoiZmF2ZXJ0b24tbnV4dDMifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **faverton-app** | ✅ Ready ([Inspect](https://vercel.com/rouhs-projects/faverton-app/6FFHjYK9q1r47ZRwfvCpQ8xMMGoP)) | [Visit Preview](https://faverton-app-git-feat-74drow-our-chois-surface-rouhs-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/faverton-app-git-feat-74drow-our-chois-surface-rouhs-projects.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 4:48pm |


author_association: NONE
user: login: Huor97
created_at: 2025-04-28T16:52:53Z
body: ![](https://github.trello.services/images/mini-trello-icon.png) [Simulation - surface](https://trello.com/c/hFa4rejS/74-simulation-surface)
author_association: OWNER