From f32fe3e4e8add15bc87c9889c1eb2046565cd787 Mon Sep 17 00:00:00 2001 From: ElPoyo Date: Mon, 8 Jun 2026 17:21:05 +0200 Subject: [PATCH] feat(ui): improve equipment add page (mobile + bufix) --- .gitignore | 1 + em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache | 28 +- em2rp/CHANGELOG.md | 3 + em2rp/functions/package-lock.json | 502 ++++- em2rp/functions/src/travel.js | 2 +- em2rp/lib/config/app_version.dart | 2 +- em2rp/lib/providers/event_provider.dart | 17 +- .../event/equipment_selection_dialog.dart | 1747 +++++++++-------- em2rp/web/version.json | 6 +- 9 files changed, 1482 insertions(+), 826 deletions(-) diff --git a/.gitignore b/.gitignore index 12a282b..e6d5d24 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,4 @@ app.*.symbols .gclient_previous_custom_vars .gclient_previous_sync_commits em2rp/lib/config/env.dart + diff --git a/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache b/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache index 17e02a3..93bcadc 100644 --- a/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache +++ b/em2rp/.firebase/hosting.YnVpbGRcd2Vi.cache @@ -19,6 +19,7 @@ canvaskit/canvaskit.js,1759914809082,bb9141a62dec1f0a41e311b845569915df9ebb5f074 canvaskit/chromium/canvaskit.wasm,1759914809184,4a868d7961a9740ae6694f62fc15b2b0ed76df50598e8311d61e8ee814d78229 canvaskit/chromium/canvaskit.js.symbols,1759914809141,f395278c466a0eaed0201edd6b14a3aa8fee0a16bfedee2d239835cd7e865472 canvaskit/chromium/canvaskit.js,1759914809136,ce5184f74e2501d849490df34d0506167a0708b9120be088039b785343335664 +assets/packages/flutter_map/lib/assets/flutter_map_logo.png,1780664966224,26fe50c9203ccf93512b80d4ee1a7578184a910457b36a6a5b7d41b799efb966 assets/packages/flutter_dropzone_web/assets/flutter_dropzone.js,1748366257688,d640313cd6a02692249cd41e4643c2771b4202cc84e0f07f5f65cdc77a36826f assets/assets/Google__G__logo.svg,1741027482182,b365d560438f8f04caf08ffaa5d8995eff6c09b36b4483f44d6f5f82d8559d4f assets/assets/google.png,1741029771653,537ca60ffa74c28eca4e62d249237403a7d47d2bc90bb90431b8d5aa923a92ee @@ -34,17 +35,16 @@ assets/assets/images/tshirt-incrust.webp,1737393735487,af7cb34adfca19c0b41c8eb63 assets/assets/icons/truss.svg,1761734811263,8ddfbbb4f96de5614348eb23fa55f61b2eb1edb064719a8bbd791c35883ec4cc assets/assets/icons/tape.svg,1761734809221,631183f0ff972aa4dc3f9f51dc7abd41a607df749d1f9a44fa7e77202d95ccde assets/assets/icons/flight-case.svg,1761734822495,0cef47fdf5d7efdd110763c32f792ef9735df35c4f42ae7d02d5fbda40e6148d -version.json,1780665878737,68547869ca05df156c49f1b529d27d6b3f9aa32f5e172ba673c2d6bfd369a89e -index.html,1780665885661,7dd070b4e8d51aa49d899690a8e2603bf8de2bd9e31f202a84d3f19ff280ae4b -flutter_service_worker.js,1780665975318,9533de2726499249f727cb931dcb97b27a8ac01f13ccf6aa299c8d7eafb5e71e -assets/FontManifest.json,1780665970544,e38b95988f5d060cf9b7ce97cb5ac9236d6f4cc04a11d69567df97b2b4cbc5e5 -assets/AssetManifest.json,1780665970542,a75793b913dabecfa1b5eade2735ec6f8f280210572cf25d0ce010d2a6d95e8f -assets/AssetManifest.bin,1780665970544,61f45b0f17a664717759ebf0f1271212f4658bd4b83e35bd5d134689b73a01d6 -assets/AssetManifest.bin.json,1780665970542,a26149597dbbee9aeaec0377943061e2b76c73bf974246f6af0b1a82e7455eaa -flutter_bootstrap.js,1780665885636,0806f900f0da893ed0ee72b8fc42b6bed886a3c74165d69e1db4cedf41559e2a -assets/packages/flutter_map/lib/assets/flutter_map_logo.png,1780664966224,26fe50c9203ccf93512b80d4ee1a7578184a910457b36a6a5b7d41b799efb966 -assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1780665974258,d41473de1f7708a0702d7f19327693486512db442f6ab0cf7774e6d6576f9fcb -assets/shaders/ink_sparkle.frag,1780665970843,591c7517d5cb43eb91ea451e0d3f9f585cbf8298cf6c46a9144b77cb0775a406 -assets/fonts/MaterialIcons-Regular.otf,1780665974268,1551d6f32ad2bc8ed771c39fdef3fc01f0188517d44127e47ad826188ca8c82f -assets/NOTICES,1780665970544,102c38a40984e6287801c2c98c5920dab4edead49226e54933bf706c4d1bb18e -main.dart.js,1780665969500,785425f0c4c27c7197b0ed160f75ef9f98b250e39141df71b471d0138bf38888 +version.json,1780684985789,5b31e1bebe4b07f1612f0ca0723533e4fa9af717cce5aaf7f436b67a01921ec5 +index.html,1780684991212,7dd070b4e8d51aa49d899690a8e2603bf8de2bd9e31f202a84d3f19ff280ae4b +assets/FontManifest.json,1780685062499,e38b95988f5d060cf9b7ce97cb5ac9236d6f4cc04a11d69567df97b2b4cbc5e5 +flutter_bootstrap.js,1780684991197,dcaa7d74bebad3b4e933b0d006a51aafff48543ae49b65d2d8d9819d5f57b6d3 +flutter_service_worker.js,1780685065763,b0206b1cc6753839a88d8dfb841e0eb53a68068a2b28a497bee28ce58f204ece +assets/AssetManifest.json,1780685062499,a75793b913dabecfa1b5eade2735ec6f8f280210572cf25d0ce010d2a6d95e8f +assets/AssetManifest.bin.json,1780685062499,a26149597dbbee9aeaec0377943061e2b76c73bf974246f6af0b1a82e7455eaa +assets/AssetManifest.bin,1780685062499,61f45b0f17a664717759ebf0f1271212f4658bd4b83e35bd5d134689b73a01d6 +assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1780685065006,d41473de1f7708a0702d7f19327693486512db442f6ab0cf7774e6d6576f9fcb +assets/shaders/ink_sparkle.frag,1780685062685,591c7517d5cb43eb91ea451e0d3f9f585cbf8298cf6c46a9144b77cb0775a406 +assets/fonts/MaterialIcons-Regular.otf,1780685065009,1551d6f32ad2bc8ed771c39fdef3fc01f0188517d44127e47ad826188ca8c82f +assets/NOTICES,1780685062499,102c38a40984e6287801c2c98c5920dab4edead49226e54933bf706c4d1bb18e +main.dart.js,1780685061297,be3f32c15b5c877c1a279d4ff0abca5928c7551d58d35e0c6e311bc4859b5e91 diff --git a/em2rp/CHANGELOG.md b/em2rp/CHANGELOG.md index 7457d2f..1d3281b 100644 --- a/em2rp/CHANGELOG.md +++ b/em2rp/CHANGELOG.md @@ -2,6 +2,9 @@ Toutes les modifications notables de ce projet seront documentées dans ce fichier. +## 05/06/2026bis +Fix bug vue mobile, amélioration de la vue de calcule des frais de déplacement + ## 05/06/2026 Ajout de la fonctionnalité de génération d'itinéraire avec calcul de coût. diff --git a/em2rp/functions/package-lock.json b/em2rp/functions/package-lock.json index fdbcf48..8b2cfb0 100644 --- a/em2rp/functions/package-lock.json +++ b/em2rp/functions/package-lock.json @@ -9,7 +9,9 @@ "@google-cloud/storage": "^7.18.0", "@google-cloud/text-to-speech": "^5.4.0", "@google/generative-ai": "^0.21.0", + "@mapbox/polyline": "^1.2.1", "axios": "^1.13.2", + "csv-parser": "^3.2.1", "dotenv": "^17.2.3", "envdot": "^0.0.3", "firebase-admin": "^12.6.0", @@ -44,7 +46,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -187,7 +188,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -1336,6 +1336,17 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@mapbox/polyline": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@mapbox/polyline/-/polyline-1.2.1.tgz", + "integrity": "sha512-sn0V18O3OzW4RCcPoUIVDWvEGQaBNH9a0y5lgqrf5hUycyw1CzrhEoxV5irzrMNXKCkw1xRsZXcaVbsVZggHXA==", + "dependencies": { + "meow": "^9.0.0" + }, + "bin": { + "polyline": "bin/polyline.bin.js" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1659,6 +1670,12 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "license": "MIT" }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -1674,6 +1691,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -2315,12 +2338,28 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "license": "MIT", + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001718", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", @@ -2554,6 +2593,18 @@ "node": ">= 8" } }, + "node_modules/csv-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.1.tgz", + "integrity": "sha512-v8RPMSglouR9od735SnwSxLBbCJqEPSbgm1R5qfr8yIiMUCEFjox56kRZid0SvgHJEkxeIEu3+a9QS3YRh7CuA==", + "license": "MIT", + "bin": { + "csv-parser": "bin/csv-parser" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -2571,6 +2622,40 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -2789,7 +2874,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -3830,6 +3914,15 @@ "uglify-js": "^3.1.4" } }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3879,6 +3972,36 @@ "node": ">= 0.4" } }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -4042,6 +4165,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4073,14 +4205,12 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -4154,6 +4284,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4867,7 +5006,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4916,7 +5054,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -5049,6 +5186,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -5092,7 +5238,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -5262,6 +5407,18 @@ "tmpl": "1.0.5" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5280,6 +5437,53 @@ "node": ">= 0.6" } }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "license": "MIT", + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -5362,6 +5566,15 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5384,6 +5597,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "license": "MIT", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5464,6 +5700,33 @@ "node": ">=6.0.0" } }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5607,7 +5870,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5630,7 +5892,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -5658,7 +5919,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5688,7 +5948,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/path-to-regexp": { @@ -5701,7 +5960,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -5966,6 +6224,15 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6026,6 +6293,135 @@ "dev": true, "license": "MIT" }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -6040,6 +6436,19 @@ "node": ">= 6" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6053,7 +6462,6 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -6444,6 +6852,38 @@ "source-map": "^0.6.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "license": "CC0-1.0" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6567,6 +7007,18 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6615,7 +7067,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6735,6 +7186,15 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ts-deepmerge": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz", @@ -6909,6 +7369,16 @@ "node": ">=10.12.0" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/em2rp/functions/src/travel.js b/em2rp/functions/src/travel.js index 4508d35..646dc5a 100644 --- a/em2rp/functions/src/travel.js +++ b/em2rp/functions/src/travel.js @@ -250,7 +250,7 @@ exports.googleMapsAutocomplete = async (req, res) => { url.searchParams.set('key', apiKey); url.searchParams.set('language', 'fr'); url.searchParams.set('components', 'country:fr'); - url.searchParams.set('types', 'address'); + url.searchParams.set('types', 'geocode'); const gRes = await axios.get(url.toString(), { timeout: 5000 }); return res.status(200).json(gRes.data); diff --git a/em2rp/lib/config/app_version.dart b/em2rp/lib/config/app_version.dart index 7628252..b667486 100644 --- a/em2rp/lib/config/app_version.dart +++ b/em2rp/lib/config/app_version.dart @@ -1,6 +1,6 @@ /// Configuration de la version de l'application class AppVersion { - static const String version = '1.2.7'; + static const String version = '1.2.8'; /// Retourne la version complète de l'application static String get fullVersion => 'v$version'; diff --git a/em2rp/lib/providers/event_provider.dart b/em2rp/lib/providers/event_provider.dart index 88f9d38..204e44e 100644 --- a/em2rp/lib/providers/event_provider.dart +++ b/em2rp/lib/providers/event_provider.dart @@ -287,14 +287,21 @@ class EventProvider with ChangeNotifier { /// Ajouter un nouvel événement Future addEvent(EventModel event) async { try { - // Ajouter l'événement localement dans _events - _events.add(event); - - // Ajouter dans le cache par mois final monthKey = '${event.startDateTime.year}-${event.startDateTime.month.toString().padLeft(2, '0')}'; + + // Ajouter dans le cache par mois s'il est chargé if (_eventsByMonth.containsKey(monthKey)) { - _eventsByMonth[monthKey]!.add(event); + if (!_eventsByMonth[monthKey]!.any((e) => e.id == event.id)) { + _eventsByMonth[monthKey]!.add(event); + } + } + + // Si _events est la même instance de liste, il a déjà été ajouté. + // Sinon (par exemple si on affiche un autre mois ou cache désynchronisé), + // on l'ajoute à _events uniquement si c'est pour le mois courant. + if ((_currentMonth == null || _currentMonth == monthKey) && !_events.any((e) => e.id == event.id)) { + _events.add(event); } notifyListeners(); diff --git a/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart b/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart index 0cdb28a..d91a742 100644 --- a/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart +++ b/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart @@ -14,9 +14,9 @@ enum SelectionType { equipment, container } /// Statut de conflit pour un conteneur enum ContainerConflictStatus { - none, // Aucun conflit - partial, // Au moins un enfant en conflit - complete, // Tous les enfants en conflit + none, // Aucun conflit + partial, // Au moins un enfant en conflit + complete, // Tous les enfants en conflit } /// Informations sur les conflits d'un conteneur @@ -82,39 +82,49 @@ class EquipmentSelectionDialog extends StatefulWidget { }); @override - State createState() => _EquipmentSelectionDialogState(); + State createState() => + _EquipmentSelectionDialogState(); } class _EquipmentSelectionDialogState extends State { final TextEditingController _searchController = TextEditingController(); - final ScrollController _scrollController = ScrollController(); // Préserve la position de scroll + final ScrollController _scrollController = + ScrollController(); // Préserve la position de scroll final DataService _dataService = DataService(apiService); EquipmentCategory? _selectedCategory; Map _selectedItems = {}; - final ValueNotifier _selectionChangeNotifier = ValueNotifier(0); // Pour notifier les changements de sélection sans setState + final ValueNotifier _selectionChangeNotifier = ValueNotifier( + 0); // Pour notifier les changements de sélection sans setState final Map _availableQuantities = {}; // Pour consommables - final Map> _recommendedContainers = {}; // Recommandations - final Map> _equipmentConflicts = {}; // Conflits de disponibilité (détaillés) - final Map _containerConflicts = {}; // Conflits des conteneurs - final Set _expandedContainers = {}; // Conteneurs dépliés dans la liste + final Map> _recommendedContainers = + {}; // Recommandations + final Map> _equipmentConflicts = + {}; // Conflits de disponibilité (détaillés) + final Map _containerConflicts = + {}; // Conflits des conteneurs + final Set _expandedContainers = + {}; // Conteneurs dépliés dans la liste // NOUVEAU : IDs en conflit récupérés en batch Set _conflictingEquipmentIds = {}; Set _conflictingContainerIds = {}; Map _conflictDetails = {}; // Détails des conflits par ID - Map _equipmentQuantities = {}; // Infos de quantités pour câbles/consommables + Map _equipmentQuantities = + {}; // Infos de quantités pour câbles/consommables bool _isLoadingConflicts = false; String _searchQuery = ''; final _searchDebouncer = Debouncer(); // Nouvelles options d'affichage - bool _showConflictingItems = false; // Afficher les équipements/boîtes en conflit + bool _showConflictingItems = + false; // Afficher les équipements/boîtes en conflit // NOUVEAU : Lazy loading et pagination - SelectionType _displayType = SelectionType.equipment; // Type affiché (équipements OU containers) + SelectionType _displayType = + SelectionType.equipment; // Type affiché (équipements OU containers) bool _isLoadingMore = false; bool _hasMoreEquipments = true; bool _hasMoreContainers = true; @@ -143,11 +153,13 @@ class _EquipmentSelectionDialogState extends State { if (_isLoadingMore) return; if (_scrollController.hasClients && - _scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) { + _scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 300) { // Charger la page suivante selon le type affiché if (_displayType == SelectionType.equipment && _hasMoreEquipments) { _loadNextEquipmentPage(); - } else if (_displayType == SelectionType.container && _hasMoreContainers) { + } else if (_displayType == SelectionType.container && + _hasMoreContainers) { _loadNextContainerPage(); } } @@ -190,31 +202,24 @@ class _EquipmentSelectionDialogState extends State { // Ajouter les conteneurs déjà assignés if (widget.alreadyAssignedContainers.isNotEmpty) { try { - // Pour les conteneurs déjà assignés, on va les chercher via l'API si nécessaire - // ou créer des conteneurs temporaires - for (var containerId in widget.alreadyAssignedContainers) { - // Chercher dans le cache ou créer un conteneur temporaire - final container = _cachedContainers.firstWhere( - (c) => c.id == containerId, - orElse: () => ContainerModel( - id: containerId, - name: 'Conteneur $containerId', - type: ContainerType.flightCase, - status: EquipmentStatus.available, - equipmentIds: [], - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - ), - ); + final containerMaps = await _dataService + .getContainersByIds(widget.alreadyAssignedContainers); + for (var map in containerMaps) { + final id = map['id'] as String; + final container = ContainerModel.fromMap(map, id); - initialSelection[containerId] = SelectedItem( - id: containerId, + if (!_cachedContainers.any((c) => c.id == id)) { + _cachedContainers.add(container); + } + + initialSelection[id] = SelectedItem( + id: id, name: container.name, type: SelectionType.container, ); // Charger le cache des enfants - _containerEquipmentCache[containerId] = List.from(container.equipmentIds); + _containerEquipmentCache[id] = List.from(container.equipmentIds); // Ajouter les enfants comme sélectionnés aussi for (var equipmentId in container.equipmentIds) { @@ -229,7 +234,9 @@ class _EquipmentSelectionDialogState extends State { } } } catch (e) { - DebugLog.error('[EquipmentSelectionDialog] Error loading already assigned containers', e); + DebugLog.error( + '[EquipmentSelectionDialog] Error loading already assigned containers', + e); } } @@ -239,7 +246,8 @@ class _EquipmentSelectionDialogState extends State { _selectedItems = initialSelection; }); _selectionChangeNotifier.value++; - DebugLog.info('[EquipmentSelectionDialog] Initialized with ${_selectedItems.length} already assigned items'); + DebugLog.info( + '[EquipmentSelectionDialog] Initialized with ${_selectedItems.length} already assigned items'); } } @@ -254,18 +262,18 @@ class _EquipmentSelectionDialogState extends State { limit: 50, startAfter: _lastEquipmentId, searchQuery: _searchQuery.isNotEmpty ? _searchQuery : null, - category: _selectedCategory != null ? equipmentCategoryToString(_selectedCategory!) : null, + category: _selectedCategory != null + ? equipmentCategoryToString(_selectedCategory!) + : null, sortBy: 'id', sortOrder: 'asc', ); - final newEquipments = (result['equipments'] as List) - .map((data) { - final map = data as Map; - final id = map['id'] as String; - return EquipmentModel.fromMap(map, id); - }) - .toList(); + final newEquipments = (result['equipments'] as List).map((data) { + final map = data as Map; + final id = map['id'] as String; + return EquipmentModel.fromMap(map, id); + }).toList(); if (mounted) { setState(() { @@ -275,18 +283,20 @@ class _EquipmentSelectionDialogState extends State { _isLoadingMore = false; }); - DebugLog.info('[EquipmentSelectionDialog] Loaded ${newEquipments.length} equipments, total: ${_paginatedEquipments.length}, hasMore: $_hasMoreEquipments'); + DebugLog.info( + '[EquipmentSelectionDialog] Loaded ${newEquipments.length} equipments, total: ${_paginatedEquipments.length}, hasMore: $_hasMoreEquipments'); // Charger les quantites pour les consommables/cbles de cette page _loadAvailableQuantities(newEquipments); - + // Vrifier si on doit charger d'autres lments (ex: tout a t filtr) WidgetsBinding.instance.addPostFrameCallback((_) { _checkIfMoreItemsNeeded(); }); } } catch (e) { - DebugLog.error('[EquipmentSelectionDialog] Error loading equipment page', e); + DebugLog.error( + '[EquipmentSelectionDialog] Error loading equipment page', e); if (mounted) { setState(() => _isLoadingMore = false); } @@ -311,7 +321,8 @@ class _EquipmentSelectionDialogState extends State { final containersData = result['containers'] as List; - DebugLog.info('[EquipmentSelectionDialog] Raw containers data received: ${containersData.length} containers'); + DebugLog.info( + '[EquipmentSelectionDialog] Raw containers data received: ${containersData.length} containers'); // D'abord, extraire TOUS les équipements final List allEquipmentsToCache = []; @@ -322,29 +333,30 @@ class _EquipmentSelectionDialogState extends State { // Debug: vérifier si le champ 'equipment' existe final hasEquipmentField = map.containsKey('equipment'); final equipmentData = map['equipment']; - DebugLog.info('[EquipmentSelectionDialog] Container $containerId: hasEquipmentField=$hasEquipmentField, equipmentData type=${equipmentData?.runtimeType}, count=${equipmentData is List ? equipmentData.length : 0}'); + DebugLog.info( + '[EquipmentSelectionDialog] Container $containerId: hasEquipmentField=$hasEquipmentField, equipmentData type=${equipmentData?.runtimeType}, count=${equipmentData is List ? equipmentData.length : 0}'); - final equipmentList = (map['equipment'] as List?) - ?.map((eqData) { - final eqMap = eqData as Map; - final eqId = eqMap['id'] as String; - DebugLog.info('[EquipmentSelectionDialog] - Equipment found: $eqId'); - return EquipmentModel.fromMap(eqMap, eqId); - }) - .toList() ?? []; + final equipmentList = + (map['equipment'] as List?)?.map((eqData) { + final eqMap = eqData as Map; + final eqId = eqMap['id'] as String; + DebugLog.info( + '[EquipmentSelectionDialog] - Equipment found: $eqId'); + return EquipmentModel.fromMap(eqMap, eqId); + }).toList() ?? + []; allEquipmentsToCache.addAll(equipmentList); } - DebugLog.info('[EquipmentSelectionDialog] Total equipments extracted from containers: ${allEquipmentsToCache.length}'); + DebugLog.info( + '[EquipmentSelectionDialog] Total equipments extracted from containers: ${allEquipmentsToCache.length}'); // Créer les containers - final newContainers = containersData - .map((data) { - final map = data as Map; - final id = map['id'] as String; - return ContainerModel.fromMap(map, id); - }) - .toList(); + final newContainers = containersData.map((data) { + final map = data as Map; + final id = map['id'] as String; + return ContainerModel.fromMap(map, id); + }).toList(); if (mounted) { setState(() { @@ -361,18 +373,21 @@ class _EquipmentSelectionDialogState extends State { _isLoadingMore = false; }); - DebugLog.info('[EquipmentSelectionDialog] Loaded ${newContainers.length} containers, total: ${_paginatedContainers.length}, hasMore: $_hasMoreContainers'); - DebugLog.info('[EquipmentSelectionDialog] Cached ${allEquipmentsToCache.length} equipment(s) from containers, total cache: ${_cachedEquipment.length}'); + DebugLog.info( + '[EquipmentSelectionDialog] Loaded ${newContainers.length} containers, total: ${_paginatedContainers.length}, hasMore: $_hasMoreContainers'); + DebugLog.info( + '[EquipmentSelectionDialog] Cached ${allEquipmentsToCache.length} equipment(s) from containers, total cache: ${_cachedEquipment.length}'); // Mettre jour les statuts de conflit pour les nouveaux containers await _updateContainerConflictStatus(); - + WidgetsBinding.instance.addPostFrameCallback((_) { _checkIfMoreItemsNeeded(); }); } } catch (e) { - DebugLog.error('[EquipmentSelectionDialog] Error loading container page', e); + DebugLog.error( + '[EquipmentSelectionDialog] Error loading container page', e); if (mounted) { setState(() => _isLoadingMore = false); } @@ -399,16 +414,19 @@ class _EquipmentSelectionDialogState extends State { void _checkIfMoreItemsNeeded() { if (!mounted || _isLoadingMore) return; - + int visibleItems = 0; if (_displayType == SelectionType.equipment) { visibleItems = _paginatedEquipments.where((eq) { - return _showConflictingItems || !_conflictingEquipmentIds.contains(eq.id); + return _showConflictingItems || + !_conflictingEquipmentIds.contains(eq.id); }).length; - + if (visibleItems < 15 && _hasMoreEquipments) { _loadNextEquipmentPage(); - } else if (_scrollController.hasClients && _scrollController.position.maxScrollExtent <= 0 && _hasMoreEquipments) { + } else if (_scrollController.hasClients && + _scrollController.position.maxScrollExtent <= 0 && + _hasMoreEquipments) { _loadNextEquipmentPage(); } } else { @@ -422,10 +440,12 @@ class _EquipmentSelectionDialogState extends State { } return true; }).length; - + if (visibleItems < 15 && _hasMoreContainers) { _loadNextContainerPage(); - } else if (_scrollController.hasClients && _scrollController.position.maxScrollExtent <= 0 && _hasMoreContainers) { + } else if (_scrollController.hasClients && + _scrollController.position.maxScrollExtent <= 0 && + _hasMoreContainers) { _loadNextContainerPage(); } } @@ -473,7 +493,8 @@ class _EquipmentSelectionDialogState extends State { setState(() => _isLoadingConflicts = true); try { - DebugLog.info('[EquipmentSelectionDialog] Loading conflicts (optimized batch method)...'); + DebugLog.info( + '[EquipmentSelectionDialog] Loading conflicts (optimized batch method)...'); final startTime = DateTime.now(); @@ -489,22 +510,31 @@ class _EquipmentSelectionDialogState extends State { final endTime = DateTime.now(); final duration = endTime.difference(startTime); - DebugLog.info('[EquipmentSelectionDialog] Conflicts loaded in ${duration.inMilliseconds}ms'); + DebugLog.info( + '[EquipmentSelectionDialog] Conflicts loaded in ${duration.inMilliseconds}ms'); // Extraire les IDs en conflit - final conflictingEquipmentIds = (result['conflictingEquipmentIds'] as List?) - ?.map((e) => e.toString()) - .toSet() ?? {}; + final conflictingEquipmentIds = + (result['conflictingEquipmentIds'] as List?) + ?.map((e) => e.toString()) + .toSet() ?? + {}; - final conflictingContainerIds = (result['conflictingContainerIds'] as List?) - ?.map((e) => e.toString()) - .toSet() ?? {}; + final conflictingContainerIds = + (result['conflictingContainerIds'] as List?) + ?.map((e) => e.toString()) + .toSet() ?? + {}; - final conflictDetails = result['conflictDetails'] as Map? ?? {}; - final equipmentQuantities = result['equipmentQuantities'] as Map? ?? {}; + final conflictDetails = + result['conflictDetails'] as Map? ?? {}; + final equipmentQuantities = + result['equipmentQuantities'] as Map? ?? {}; - DebugLog.info('[EquipmentSelectionDialog] Found ${conflictingEquipmentIds.length} equipment(s) and ${conflictingContainerIds.length} container(s) in conflict'); - DebugLog.info('[EquipmentSelectionDialog] Quantity info for ${equipmentQuantities.length} equipment(s)'); + DebugLog.info( + '[EquipmentSelectionDialog] Found ${conflictingEquipmentIds.length} equipment(s) and ${conflictingContainerIds.length} container(s) in conflict'); + DebugLog.info( + '[EquipmentSelectionDialog] Quantity info for ${equipmentQuantities.length} equipment(s)'); if (mounted) { setState(() { @@ -524,7 +554,8 @@ class _EquipmentSelectionDialogState extends State { id: conflictMap['eventId'] as String, name: conflictMap['eventName'] as String, description: '', - startDateTime: DateTime.parse(conflictMap['startDate'] as String), + startDateTime: + DateTime.parse(conflictMap['startDate'] as String), endDateTime: DateTime.parse(conflictMap['endDate'] as String), basePrice: 0.0, installationTime: 0, @@ -543,11 +574,18 @@ class _EquipmentSelectionDialogState extends State { ); // Calculer les jours de chevauchement - final conflictStart = DateTime.parse(conflictMap['startDate'] as String); - final conflictEnd = DateTime.parse(conflictMap['endDate'] as String); - final overlapStart = widget.startDate.isAfter(conflictStart) ? widget.startDate : conflictStart; - final overlapEnd = widget.endDate.isBefore(conflictEnd) ? widget.endDate : conflictEnd; - final overlapDays = overlapEnd.difference(overlapStart).inDays + 1; + final conflictStart = + DateTime.parse(conflictMap['startDate'] as String); + final conflictEnd = + DateTime.parse(conflictMap['endDate'] as String); + final overlapStart = widget.startDate.isAfter(conflictStart) + ? widget.startDate + : conflictStart; + final overlapEnd = widget.endDate.isBefore(conflictEnd) + ? widget.endDate + : conflictEnd; + final overlapDays = + overlapEnd.difference(overlapStart).inDays + 1; return AvailabilityConflict( equipmentId: itemId, @@ -563,7 +601,6 @@ class _EquipmentSelectionDialogState extends State { // Mettre à jour les statuts de conteneurs await _updateContainerConflictStatus(); - } catch (e) { DebugLog.error('[EquipmentSelectionDialog] Error loading conflicts', e); } finally { @@ -594,9 +631,10 @@ class _EquipmentSelectionDialogState extends State { .toList(); if (conflictingChildren.isNotEmpty) { - final status = conflictingChildren.length == container.equipmentIds.length - ? ContainerConflictStatus.complete - : ContainerConflictStatus.partial; + final status = + conflictingChildren.length == container.equipmentIds.length + ? ContainerConflictStatus.complete + : ContainerConflictStatus.partial; _containerConflicts[container.id] = ContainerConflictInfo( status: status, @@ -604,18 +642,21 @@ class _EquipmentSelectionDialogState extends State { totalChildren: container.equipmentIds.length, ); - DebugLog.info('[EquipmentSelectionDialog] Container ${container.id}: ${status.name} conflict (${conflictingChildren.length}/${container.equipmentIds.length} children)'); + DebugLog.info( + '[EquipmentSelectionDialog] Container ${container.id}: ${status.name} conflict (${conflictingChildren.length}/${container.equipmentIds.length} children)'); } } - DebugLog.info('[EquipmentSelectionDialog] Total containers with conflicts: ${_containerConflicts.length}'); + DebugLog.info( + '[EquipmentSelectionDialog] Total containers with conflicts: ${_containerConflicts.length}'); // Déclencher un rebuild pour afficher les changements visuels if (mounted) { setState(() {}); } } catch (e) { - DebugLog.error('[EquipmentSelectionDialog] Error updating container conflicts', e); + DebugLog.error( + '[EquipmentSelectionDialog] Error updating container conflicts', e); } } @@ -633,7 +674,8 @@ class _EquipmentSelectionDialogState extends State { /// Construit l'affichage des quantités pour les câbles/consommables Widget _buildQuantityInfo(EquipmentModel equipment) { - final quantityInfo = _equipmentQuantities[equipment.id] as Map?; + final quantityInfo = + _equipmentQuantities[equipment.id] as Map?; if (quantityInfo == null) { // Pas d'info de quantité, utiliser l'ancien système (availableQuantities) @@ -688,7 +730,8 @@ class _EquipmentSelectionDialogState extends State { } /// Affiche un dialog avec les détails des réservations de quantité - Future _showQuantityDetailsDialog(EquipmentModel equipment, Map quantityInfo) async { + Future _showQuantityDetailsDialog( + EquipmentModel equipment, Map quantityInfo) async { final reservations = quantityInfo['reservations'] as List? ?? []; final totalQuantity = quantityInfo['totalQuantity'] as int? ?? 0; final availableQuantity = quantityInfo['availableQuantity'] as int? ?? 0; @@ -759,7 +802,9 @@ class _EquipmentSelectionDialogState extends State { '$availableQuantity $unit', style: TextStyle( fontWeight: FontWeight.bold, - color: availableQuantity > 0 ? Colors.green.shade700 : Colors.red, + color: availableQuantity > 0 + ? Colors.green.shade700 + : Colors.red, fontSize: 16, ), ), @@ -786,10 +831,12 @@ class _EquipmentSelectionDialogState extends State { child: Column( children: reservations.map((reservation) { final res = reservation as Map; - final eventName = res['eventName'] as String? ?? 'Événement inconnu'; + final eventName = + res['eventName'] as String? ?? 'Événement inconnu'; final quantity = res['quantity'] as int? ?? 0; final viaContainer = res['viaContainer'] as String?; - final viaContainerName = res['viaContainerName'] as String?; + final viaContainerName = + res['viaContainerName'] as String?; return Card( margin: const EdgeInsets.only(bottom: 8), @@ -808,7 +855,8 @@ class _EquipmentSelectionDialogState extends State { ), title: Text( eventName, - style: const TextStyle(fontWeight: FontWeight.w500), + style: + const TextStyle(fontWeight: FontWeight.w500), ), subtitle: viaContainer != null ? Text( @@ -859,9 +907,12 @@ class _EquipmentSelectionDialogState extends State { return _recommendedContainers[equipmentId] ?? []; } - void _toggleSelection(String id, String name, SelectionType type, {int? maxQuantity, bool force = false}) async { + void _toggleSelection(String id, String name, SelectionType type, + {int? maxQuantity, bool force = false}) async { // Vérifier si l'équipement est en conflit - if (!force && type == SelectionType.equipment && _conflictingEquipmentIds.contains(id)) { + if (!force && + type == SelectionType.equipment && + _conflictingEquipmentIds.contains(id)) { // Demander confirmation pour forcer final shouldForce = await _showForceConfirmationDialog(id); if (shouldForce == true) { @@ -873,7 +924,8 @@ class _EquipmentSelectionDialogState extends State { if (_selectedItems.containsKey(id)) { // Désélectionner DebugLog.info('[EquipmentSelectionDialog] Deselecting $type: $id'); - DebugLog.info('[EquipmentSelectionDialog] Before deselection, _selectedItems count: ${_selectedItems.length}'); + DebugLog.info( + '[EquipmentSelectionDialog] Before deselection, _selectedItems count: ${_selectedItems.length}'); if (type == SelectionType.container) { // Si c'est un conteneur, désélectionner d'abord ses enfants de manière asynchrone @@ -887,8 +939,10 @@ class _EquipmentSelectionDialogState extends State { }); } - DebugLog.info('[EquipmentSelectionDialog] After deselection, _selectedItems count: ${_selectedItems.length}'); - DebugLog.info('[EquipmentSelectionDialog] Remaining items: ${_selectedItems.keys.toList()}'); + DebugLog.info( + '[EquipmentSelectionDialog] After deselection, _selectedItems count: ${_selectedItems.length}'); + DebugLog.info( + '[EquipmentSelectionDialog] Remaining items: ${_selectedItems.keys.toList()}'); // Notifier le changement _selectionChangeNotifier.value++; @@ -927,7 +981,8 @@ class _EquipmentSelectionDialogState extends State { Future _selectContainerChildren(String containerId) async { try { // Chercher le container dans les données paginées ou le cache - final container = [..._paginatedContainers, ..._cachedContainers].firstWhere( + final container = + [..._paginatedContainers, ..._cachedContainers].firstWhere( (c) => c.id == containerId, orElse: () => ContainerModel( id: containerId, @@ -973,7 +1028,8 @@ class _EquipmentSelectionDialogState extends State { } } - DebugLog.info('[EquipmentSelectionDialog] Selected container $containerId with ${container.equipmentIds.length} children'); + DebugLog.info( + '[EquipmentSelectionDialog] Selected container $containerId with ${container.equipmentIds.length} children'); } catch (e) { DebugLog.error('Error selecting container children', e); } @@ -983,7 +1039,8 @@ class _EquipmentSelectionDialogState extends State { Future _deselectContainerChildren(String containerId) async { try { // Chercher le container dans les données paginées ou le cache - final container = [..._paginatedContainers, ..._cachedContainers].firstWhere( + final container = + [..._paginatedContainers, ..._cachedContainers].firstWhere( (c) => c.id == containerId, orElse: () => ContainerModel( id: containerId, @@ -1011,7 +1068,8 @@ class _EquipmentSelectionDialogState extends State { }); } - DebugLog.info('[EquipmentSelectionDialog] Deselected container $containerId and ${container.equipmentIds.length} children'); + DebugLog.info( + '[EquipmentSelectionDialog] Deselected container $containerId and ${container.equipmentIds.length} children'); } catch (e) { DebugLog.error('Error deselecting container children', e); } @@ -1035,33 +1093,36 @@ class _EquipmentSelectionDialogState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Cet équipement est déjà utilisé sur ${conflicts.length} événement(s) :'), + Text( + 'Cet équipement est déjà utilisé sur ${conflicts.length} événement(s) :'), const SizedBox(height: 12), ...conflicts.map((conflict) => Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon(Icons.event, size: 16, color: Colors.orange), - const SizedBox(width: 8), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - conflict.conflictingEvent.name, - style: const TextStyle(fontWeight: FontWeight.bold), + padding: const EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.event, size: 16, color: Colors.orange), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + conflict.conflictingEvent.name, + style: + const TextStyle(fontWeight: FontWeight.bold), + ), + Text( + 'Chevauchement : ${conflict.overlapDays} jour(s)', + style: const TextStyle( + fontSize: 12, color: Colors.grey), + ), + ], ), - Text( - 'Chevauchement : ${conflict.overlapDays} jour(s)', - style: const TextStyle(fontSize: 12, color: Colors.grey), - ), - ], - ), + ), + ], ), - ], - ), - )), + )), ], ), actions: [ @@ -1082,69 +1143,93 @@ class _EquipmentSelectionDialogState extends State { ); } - @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; + final isMobile = screenSize.width < 800; final dialogWidth = screenSize.width * 0.9; final dialogHeight = screenSize.height * 0.85; return Dialog( shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(isMobile ? 0 : 16), ), - child: SizedBox( - width: dialogWidth.clamp(600.0, 1200.0), - height: dialogHeight.clamp(500.0, 900.0), + insetPadding: isMobile + ? EdgeInsets.zero + : const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0), + child: Container( + width: isMobile ? screenSize.width : dialogWidth.clamp(600.0, 1200.0), + height: isMobile ? screenSize.height : dialogHeight.clamp(500.0, 900.0), child: Column( children: [ - _buildHeader(), + _buildHeader(isMobile), _buildSearchAndFilters(), Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Liste principale - Expanded( - flex: 2, - child: _buildMainList(), - ), - - // Panneau latéral : sélection + recommandations - Container( - width: 320, - decoration: BoxDecoration( - color: Colors.grey.shade50, - border: Border( - left: BorderSide(color: Colors.grey.shade300), - ), - ), - child: Column( + child: isMobile + ? Column( children: [ - Expanded( - flex: 3, + Expanded(child: _buildMainList()), + Container( + height: 250, + decoration: BoxDecoration( + color: Colors.grey.shade50, + border: Border( + top: BorderSide(color: Colors.grey.shade300), + ), + ), child: ValueListenableBuilder( valueListenable: _selectionChangeNotifier, builder: (context, _, __) => _buildSelectionPanel(), ), ), - if (_hasRecommendations) - Expanded( - flex: 2, - child: Container( - decoration: BoxDecoration( - border: Border( - top: BorderSide(color: Colors.grey.shade300), - ), - ), - child: _buildRecommendationsPanel(), + ], + ) + : Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Liste principale + Expanded( + flex: 2, + child: _buildMainList(), + ), + + // Panneau latéral : sélection + recommandations + Container( + width: 320, + decoration: BoxDecoration( + color: Colors.grey.shade50, + border: Border( + left: BorderSide(color: Colors.grey.shade300), ), ), + child: Column( + children: [ + Expanded( + flex: 3, + child: ValueListenableBuilder( + valueListenable: _selectionChangeNotifier, + builder: (context, _, __) => + _buildSelectionPanel(), + ), + ), + if (_hasRecommendations) + Expanded( + flex: 2, + child: Container( + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: Colors.grey.shade300), + ), + ), + child: _buildRecommendationsPanel(), + ), + ), + ], + ), + ), ], ), - ), - ], - ), ), _buildFooter(), ], @@ -1153,29 +1238,32 @@ class _EquipmentSelectionDialogState extends State { ); } - Widget _buildHeader() { + Widget _buildHeader(bool isMobile) { return Container( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.rouge, - borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + borderRadius: + BorderRadius.vertical(top: Radius.circular(isMobile ? 0 : 16)), ), child: Row( children: [ - const Icon(Icons.add_circle, color: Colors.white, size: 28), - const SizedBox(width: 12), + const Icon(Icons.add_circle, color: Colors.white, size: 24), + const SizedBox(width: 8), const Expanded( child: Text( 'Ajouter du matériel', style: TextStyle( color: Colors.white, - fontSize: 20, + fontSize: 18, fontWeight: FontWeight.bold, ), ), ), IconButton( icon: const Icon(Icons.close, color: Colors.white), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), onPressed: () => Navigator.of(context).pop(), ), ], @@ -1210,7 +1298,8 @@ class _EquipmentSelectionDialogState extends State { border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), - contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + contentPadding: + const EdgeInsets.symmetric(vertical: 8, horizontal: 12), ), onChanged: (value) { setState(() => _searchQuery = value.toLowerCase()); @@ -1240,76 +1329,90 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(height: 12), - // Checkbox pour afficher les équipements en conflit - Row( + // Checkbox et options d'affichage + Wrap( + spacing: 16, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, children: [ - Checkbox( - value: _showConflictingItems, - onChanged: (value) { - setState(() { - _showConflictingItems = value ?? false; - }); - }, - activeColor: AppColors.rouge, + // Checkbox pour afficher les équipements en conflit + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: _showConflictingItems, + visualDensity: VisualDensity.compact, + onChanged: (value) { + setState(() { + _showConflictingItems = value ?? false; + }); + }, + activeColor: AppColors.rouge, + ), + const Text( + 'Afficher les équipements déjà utilisés', + style: TextStyle(fontSize: 14), + ), + const SizedBox(width: 8), + Tooltip( + message: + 'Afficher les équipements et boîtes qui sont déjà utilisés durant ces dates', + child: Icon( + Icons.info_outline, + size: 18, + color: Colors.grey.shade600, + ), + ), + ], ), - const Text( - 'Afficher les équipements déjà utilisés', - style: TextStyle(fontSize: 14), - ), - const SizedBox(width: 8), - Tooltip( - message: 'Afficher les équipements et boîtes qui sont déjà utilisés durant ces dates', - child: Icon( - Icons.info_outline, - size: 18, - color: Colors.grey.shade600, - ), - ), - ], - ), - const SizedBox(height: 12), - - // Chip pour switcher entre Équipements et Containers - Row( - children: [ - const Text( - 'Afficher :', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - ), - const SizedBox(width: 8), - ChoiceChip( - label: const Text('Équipements'), - selected: _displayType == SelectionType.equipment, - onSelected: (selected) { - if (selected && _displayType != SelectionType.equipment) { - setState(() { - _displayType = SelectionType.equipment; - }); - _reloadData(); - } - }, - selectedColor: AppColors.rouge, - labelStyle: TextStyle( - color: _displayType == SelectionType.equipment ? Colors.white : Colors.black87, - ), - ), - const SizedBox(width: 8), - ChoiceChip( - label: const Text('Containers'), - selected: _displayType == SelectionType.container, - onSelected: (selected) { - if (selected && _displayType != SelectionType.container) { - setState(() { - _displayType = SelectionType.container; - }); - _reloadData(); - } - }, - selectedColor: AppColors.rouge, - labelStyle: TextStyle( - color: _displayType == SelectionType.container ? Colors.white : Colors.black87, - ), + // Chip pour switcher entre Équipements et Containers + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Afficher :', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + ), + const SizedBox(width: 8), + ChoiceChip( + label: const Text('Équipements'), + selected: _displayType == SelectionType.equipment, + onSelected: (selected) { + if (selected && _displayType != SelectionType.equipment) { + setState(() { + _displayType = SelectionType.equipment; + }); + _reloadData(); + } + }, + selectedColor: AppColors.rouge, + labelStyle: TextStyle( + color: _displayType == SelectionType.equipment + ? Colors.white + : Colors.black87, + ), + ), + const SizedBox(width: 8), + ChoiceChip( + label: const Text('Containers'), + selected: _displayType == SelectionType.container, + onSelected: (selected) { + if (selected && _displayType != SelectionType.container) { + setState(() { + _displayType = SelectionType.container; + }); + _reloadData(); + } + }, + selectedColor: AppColors.rouge, + labelStyle: TextStyle( + color: _displayType == SelectionType.container + ? Colors.white + : Colors.black87, + ), + ), + ], ), ], ), @@ -1372,14 +1475,16 @@ class _EquipmentSelectionDialogState extends State { if (_displayType == SelectionType.equipment) { // Filtrer côté client pour "Afficher équipements déjà utilisés" final filteredEquipments = _paginatedEquipments.where((eq) { - if (!_showConflictingItems && _conflictingEquipmentIds.contains(eq.id)) { + if (!_showConflictingItems && + _conflictingEquipmentIds.contains(eq.id)) { return false; } return true; }).toList(); itemWidgets = filteredEquipments.map((equipment) { - return _buildEquipmentCard(equipment, key: ValueKey('equipment_${equipment.id}')); + return _buildEquipmentCard(equipment, + key: ValueKey('equipment_${equipment.id}')); }).toList(); } else { // Containers @@ -1403,16 +1508,21 @@ class _EquipmentSelectionDialogState extends State { }).toList(); itemWidgets = filteredContainers.map((container) { - return _buildContainerCard(container, key: ValueKey('container_${container.id}')); + return _buildContainerCard(container, + key: ValueKey('container_${container.id}')); }).toList(); } return NotificationListener( onNotification: (ScrollNotification scrollInfo) { - if (!_isLoadingMore && scrollInfo.metrics.pixels >= scrollInfo.metrics.maxScrollExtent - 300) { - if (_displayType == SelectionType.equipment && _hasMoreEquipments) { + if (!_isLoadingMore && + scrollInfo.metrics.pixels >= + scrollInfo.metrics.maxScrollExtent - 300) { + if (_displayType == SelectionType.equipment && + _hasMoreEquipments) { _loadNextEquipmentPage(); - } else if (_displayType == SelectionType.container && _hasMoreContainers) { + } else if (_displayType == SelectionType.container && + _hasMoreContainers) { _loadNextContainerPage(); } } @@ -1429,8 +1539,12 @@ class _EquipmentSelectionDialogState extends State { final List allChildren = [ // Header _buildSectionHeader( - _displayType == SelectionType.equipment ? 'Équipements' : 'Containers', - _displayType == SelectionType.equipment ? Icons.inventory_2 : Icons.inventory, + _displayType == SelectionType.equipment + ? 'Équipements' + : 'Containers', + _displayType == SelectionType.equipment + ? Icons.inventory_2 + : Icons.inventory, itemWidgets.length, ), const SizedBox(height: 12), @@ -1448,13 +1562,17 @@ class _EquipmentSelectionDialogState extends State { ), // Message si fin de liste - if (!_isLoadingMore && !(_displayType == SelectionType.equipment ? _hasMoreEquipments : _hasMoreContainers)) + if (!_isLoadingMore && + !(_displayType == SelectionType.equipment + ? _hasMoreEquipments + : _hasMoreContainers)) Padding( padding: const EdgeInsets.all(16), child: Center( child: Text( 'Fin de la liste', - style: TextStyle(color: Colors.grey.shade600, fontSize: 14), + style: TextStyle( + color: Colors.grey.shade600, fontSize: 14), ), ), ), @@ -1466,11 +1584,13 @@ class _EquipmentSelectionDialogState extends State { padding: const EdgeInsets.all(32), child: Column( children: [ - Icon(Icons.search_off, size: 64, color: Colors.grey.shade400), + Icon(Icons.search_off, + size: 64, color: Colors.grey.shade400), const SizedBox(height: 16), Text( 'Aucun résultat trouvé', - style: TextStyle(fontSize: 16, color: Colors.grey.shade600), + style: TextStyle( + fontSize: 16, color: Colors.grey.shade600), ), ], ), @@ -1535,10 +1655,11 @@ class _EquipmentSelectionDialogState extends State { Widget _buildEquipmentCard(EquipmentModel equipment, {Key? key}) { final isSelected = _selectedItems.containsKey(equipment.id); final isConsumable = equipment.category == EquipmentCategory.consumable || - equipment.category == EquipmentCategory.cable; + equipment.category == EquipmentCategory.cable; final availableQty = _availableQuantities[equipment.id]; final selectedItem = _selectedItems[equipment.id]; - final hasConflict = _conflictingEquipmentIds.contains(equipment.id); // CORRECTION ICI ! + final hasConflict = + _conflictingEquipmentIds.contains(equipment.id); // CORRECTION ICI ! final conflictDetails = _getConflictDetailsFor(equipment.id); // Bloquer la sélection si en conflit et non forcé @@ -1577,240 +1698,257 @@ class _EquipmentSelectionDialogState extends State { child: Padding( padding: const EdgeInsets.all(16), child: Column( - children: [ - Row( - children: [ - // Checkbox - Checkbox( - value: isSelected, - onChanged: canSelect - ? (value) => _toggleSelection( - equipment.id, - equipment.id, - SelectionType.equipment, - maxQuantity: availableQty, - ) - : null, - activeColor: AppColors.rouge, - ), + children: [ + Row( + children: [ + // Checkbox + Checkbox( + value: isSelected, + onChanged: canSelect + ? (value) => _toggleSelection( + equipment.id, + equipment.id, + SelectionType.equipment, + maxQuantity: availableQty, + ) + : null, + activeColor: AppColors.rouge, + ), - const SizedBox(width: 12), + const SizedBox(width: 12), - // Icône - equipment.category.getIcon(size: 32, color: equipment.category.color), + // Icône + equipment.category + .getIcon(size: 32, color: equipment.category.color), - const SizedBox(width: 16), + const SizedBox(width: 16), - // Infos - Expanded( + // Infos + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + equipment.id, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + if (hasConflict) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.orange.shade700, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.warning, + size: 14, color: Colors.white), + const SizedBox(width: 4), + Text( + 'Déjà utilisé', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + if (equipment.brand != null || + equipment.model != null) + Text( + '${equipment.brand ?? ''} ${equipment.model ?? ''}' + .trim(), + style: TextStyle( + color: Colors.grey.shade700, + fontSize: 14, + ), + ), + // Affichage des boîtes parentes + if (_getParentContainers(equipment.id).isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Wrap( + spacing: 4, + runSpacing: 4, + children: _getParentContainers(equipment.id) + .map((container) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.blue.shade50, + border: Border.all( + color: Colors.blue.shade300), + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.inventory, + size: 12, + color: Colors.blue.shade700), + const SizedBox(width: 4), + Text( + container.name, + style: TextStyle( + fontSize: 10, + color: Colors.blue.shade700, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + }).toList(), + ), + ), + if (isConsumable) + Padding( + padding: const EdgeInsets.only(top: 4), + child: _buildQuantityInfo(equipment), + ), + ], + ), + ), + + // Sélecteur de quantité pour consommables (toujours affiché) + if (isConsumable && availableQty != null) + _buildQuantitySelector( + equipment.id, + selectedItem ?? + SelectedItem( + id: equipment.id, + name: equipment.id, + type: SelectionType.equipment, + quantity: 0, // Quantité 0 si non sélectionné + ), + availableQty, + isSelected: isSelected, // Passer l'état de sélection + ), + ], + ), + + // Affichage des conflits + if (hasConflict) + Container( + margin: const EdgeInsets.only(top: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.orange.shade300), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - Expanded( - child: Text( - equipment.id, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), + Icon(Icons.info_outline, + size: 16, color: Colors.orange.shade900), + const SizedBox(width: 6), + Text( + 'Utilisé sur ${conflictDetails.length} événement(s) :', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.orange.shade900, ), ), - if (hasConflict) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: Colors.orange.shade700, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.warning, size: 14, color: Colors.white), - const SizedBox(width: 4), - Text( - 'Déjà utilisé', - style: const TextStyle( - color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), ], ), - if (equipment.brand != null || equipment.model != null) - Text( - '${equipment.brand ?? ''} ${equipment.model ?? ''}'.trim(), - style: TextStyle( - color: Colors.grey.shade700, - fontSize: 14, - ), - ), - // Affichage des boîtes parentes - if (_getParentContainers(equipment.id).isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Wrap( - spacing: 4, - runSpacing: 4, - children: _getParentContainers(equipment.id).map((container) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.blue.shade50, - border: Border.all(color: Colors.blue.shade300), - borderRadius: BorderRadius.circular(10), + const SizedBox(height: 6), + ...conflictDetails.take(2).map((detail) { + final eventName = detail['eventName'] as String? ?? + 'Événement inconnu'; + final viaContainer = + detail['viaContainer'] as String?; + final viaContainerName = + detail['viaContainerName'] as String?; + + return Padding( + padding: const EdgeInsets.only(left: 22, top: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'ÔÇó $eventName', + style: TextStyle( + fontSize: 11, + color: Colors.orange.shade800, ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.inventory, size: 12, color: Colors.blue.shade700), - const SizedBox(width: 4), - Text( - container.name, - style: TextStyle( - fontSize: 10, - color: Colors.blue.shade700, - fontWeight: FontWeight.w500, - ), + ), + if (viaContainer != null) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Text( + 'via ${viaContainerName ?? viaContainer}', + style: TextStyle( + fontSize: 10, + color: Colors.grey.shade600, + fontStyle: FontStyle.italic, ), - ], + ), ), - ); - }).toList(), + ], + ), + ); + }), + if (conflictDetails.length > 2) + Padding( + padding: const EdgeInsets.only(left: 22, top: 4), + child: Text( + '... et ${conflictDetails.length - 2} autre(s)', + style: TextStyle( + fontSize: 11, + color: Colors.orange.shade800, + fontStyle: FontStyle.italic, + ), ), ), - if (isConsumable) + if (!isSelected) Padding( - padding: const EdgeInsets.only(top: 4), - child: _buildQuantityInfo(equipment), + padding: const EdgeInsets.only(top: 8), + child: TextButton.icon( + onPressed: () => _toggleSelection( + equipment.id, + equipment.id, + SelectionType.equipment, + maxQuantity: availableQty, + force: true, + ), + icon: const Icon(Icons.warning, size: 16), + label: const Text('Forcer quand même', + style: TextStyle(fontSize: 12)), + style: TextButton.styleFrom( + foregroundColor: Colors.orange.shade900, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + ), + ), ), ], ), ), - - // Sélecteur de quantité pour consommables (toujours affiché) - if (isConsumable && availableQty != null) - _buildQuantitySelector( - equipment.id, - selectedItem ?? SelectedItem( - id: equipment.id, - name: equipment.id, - type: SelectionType.equipment, - quantity: 0, // Quantité 0 si non sélectionné - ), - availableQty, - isSelected: isSelected, // Passer l'état de sélection - ), - ], - ), - - // Affichage des conflits - if (hasConflict) - Container( - margin: const EdgeInsets.only(top: 12), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.orange.shade100, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.orange.shade300), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(Icons.info_outline, size: 16, color: Colors.orange.shade900), - const SizedBox(width: 6), - Text( - 'Utilisé sur ${conflictDetails.length} événement(s) :', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.orange.shade900, - ), - ), - ], - ), - const SizedBox(height: 6), - ...conflictDetails.take(2).map((detail) { - final eventName = detail['eventName'] as String? ?? 'Événement inconnu'; - final viaContainer = detail['viaContainer'] as String?; - final viaContainerName = detail['viaContainerName'] as String?; - - return Padding( - padding: const EdgeInsets.only(left: 22, top: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'ÔÇó $eventName', - style: TextStyle( - fontSize: 11, - color: Colors.orange.shade800, - ), - ), - if (viaContainer != null) - Padding( - padding: const EdgeInsets.only(left: 8), - child: Text( - 'via ${viaContainerName ?? viaContainer}', - style: TextStyle( - fontSize: 10, - color: Colors.grey.shade600, - fontStyle: FontStyle.italic, - ), - ), - ), - ], - ), - ); - }), - if (conflictDetails.length > 2) - Padding( - padding: const EdgeInsets.only(left: 22, top: 4), - child: Text( - '... et ${conflictDetails.length - 2} autre(s)', - style: TextStyle( - fontSize: 11, - color: Colors.orange.shade800, - fontStyle: FontStyle.italic, - ), - ), - ), - if (!isSelected) - Padding( - padding: const EdgeInsets.only(top: 8), - child: TextButton.icon( - onPressed: () => _toggleSelection( - equipment.id, - equipment.id, - SelectionType.equipment, - maxQuantity: availableQty, - force: true, - ), - icon: const Icon(Icons.warning, size: 16), - label: const Text('Forcer quand même', style: TextStyle(fontSize: 12)), - style: TextButton.styleFrom( - foregroundColor: Colors.orange.shade900, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - ), - ), - ), - ], - ), - ), - ], + ], + ), ), ), ), ), - ), - ); + ); } /// Widget pour le sélecteur de quantité @@ -1829,18 +1967,21 @@ class _EquipmentSelectionDialogState extends State { children: [ IconButton( icon: const Icon(Icons.remove_circle_outline), - onPressed: isSelected && selectedItem.quantity > 1 - ? () { - if (mounted) { - setState(() { - _selectedItems[equipmentId] = selectedItem.copyWith(quantity: selectedItem.quantity - 1); - }); - _selectionChangeNotifier.value++; // Notifier le changement - } - } - : null, + onPressed: () { + if (isSelected && selectedItem.quantity > 1) { + if (mounted) { + setState(() { + _selectedItems[equipmentId] = selectedItem.copyWith( + quantity: selectedItem.quantity - 1); + }); + _selectionChangeNotifier.value++; // Notifier le changement + } + } + }, iconSize: 20, - color: isSelected && selectedItem.quantity > 1 ? AppColors.rouge : Colors.grey, + color: isSelected && selectedItem.quantity > 1 + ? AppColors.rouge + : Colors.grey, ), Expanded( child: Text( @@ -1854,42 +1995,47 @@ class _EquipmentSelectionDialogState extends State { ), IconButton( icon: const Icon(Icons.add_circle_outline), - onPressed: (isSelected && selectedItem.quantity < maxQuantity) || !isSelected - ? () { - if (!isSelected) { - // Premier clic : sélectionner avec quantité 1 - _toggleSelection( - equipmentId, - selectedItem.name, - SelectionType.equipment, - maxQuantity: maxQuantity, - ); - } else { - // Item déjà sélectionné : incrémenter - if (mounted) { - setState(() { - _selectedItems[equipmentId] = selectedItem.copyWith(quantity: selectedItem.quantity + 1); - }); - _selectionChangeNotifier.value++; // Notifier le changement - } - } + onPressed: () { + if ((isSelected && selectedItem.quantity < maxQuantity) || + !isSelected) { + if (!isSelected) { + // Premier clic : sélectionner avec quantité 1 + _toggleSelection( + equipmentId, + selectedItem.name, + SelectionType.equipment, + maxQuantity: maxQuantity, + ); + } else { + // Item déjà sélectionné : incrémenter + if (mounted) { + setState(() { + _selectedItems[equipmentId] = selectedItem.copyWith( + quantity: selectedItem.quantity + 1); + }); + _selectionChangeNotifier.value++; // Notifier le changement } - : null, + } + } + }, iconSize: 20, - color: AppColors.rouge, + color: ((isSelected && selectedItem.quantity < maxQuantity) || + !isSelected) + ? AppColors.rouge + : Colors.grey, ), ], ), ); } - Widget _buildContainerCard(ContainerModel container, {Key? key}) { final isSelected = _selectedItems.containsKey(container.id); final isExpanded = _expandedContainers.contains(container.id); final conflictInfo = _containerConflicts[container.id]; final hasConflict = conflictInfo != null; - final isCompleteConflict = conflictInfo?.status == ContainerConflictStatus.complete; + final isCompleteConflict = + conflictInfo?.status == ContainerConflictStatus.complete; // Bloquer la sélection si tous les enfants sont en conflit (sauf si déjà sélectionné) final canSelect = !isCompleteConflict || isSelected; @@ -1898,240 +2044,263 @@ class _EquipmentSelectionDialogState extends State { key: key, child: Card( margin: const EdgeInsets.only(bottom: 12), - elevation: isSelected ? 4 : 1, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: isSelected - ? const BorderSide(color: AppColors.rouge, width: 2) - : hasConflict - ? BorderSide( - color: isCompleteConflict ? Colors.red.shade300 : Colors.orange.shade300, - width: 1, - ) - : BorderSide.none, - ), - child: Container( - decoration: hasConflict && !isSelected - ? BoxDecoration( - color: isCompleteConflict ? Colors.red.shade50 : Colors.orange.shade50, + elevation: isSelected ? 4 : 1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: isSelected + ? const BorderSide(color: AppColors.rouge, width: 2) + : hasConflict + ? BorderSide( + color: isCompleteConflict + ? Colors.red.shade300 + : Colors.orange.shade300, + width: 1, + ) + : BorderSide.none, + ), + child: Container( + decoration: hasConflict && !isSelected + ? BoxDecoration( + color: isCompleteConflict + ? Colors.red.shade50 + : Colors.orange.shade50, + borderRadius: BorderRadius.circular(8), + ) + : null, + child: Column( + children: [ + InkWell( + onTap: canSelect + ? () => _toggleSelection( + container.id, + container.name, + SelectionType.container, + ) + : null, borderRadius: BorderRadius.circular(8), - ) - : null, - child: Column( - children: [ - InkWell( - onTap: canSelect - ? () => _toggleSelection( - container.id, - container.name, - SelectionType.container, - ) - : null, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - Row( - children: [ - // Checkbox - Checkbox( - value: isSelected, - onChanged: canSelect - ? (value) => _toggleSelection( - container.id, - container.name, - SelectionType.container, - ) - : null, - activeColor: AppColors.rouge, - ), - - const SizedBox(width: 12), - - // Icône du conteneur - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: AppColors.rouge.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: container.type.getIcon(size: 28, color: AppColors.rouge), - ), - - const SizedBox(width: 16), - - // Infos principales - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + children: [ + // Checkbox + Checkbox( + value: isSelected, + onChanged: canSelect + ? (value) => _toggleSelection( container.id, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, + container.name, + SelectionType.container, + ) + : null, + activeColor: AppColors.rouge, + ), + + const SizedBox(width: 12), + + // Icône du conteneur + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: AppColors.rouge.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: container.type + .getIcon(size: 28, color: AppColors.rouge), + ), + + const SizedBox(width: 16), + + // Infos principales + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + container.id, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), ), ), - ), - // Badge de statut de conflit - if (hasConflict) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: isCompleteConflict - ? Colors.red.shade700 - : Colors.orange.shade700, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - isCompleteConflict ? Icons.block : Icons.warning, - size: 14, - color: Colors.white, - ), - const SizedBox(width: 4), - Text( - isCompleteConflict ? 'Indisponible' : 'Partiellement utilisée', - style: const TextStyle( + // Badge de statut de conflit + if (hasConflict) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: isCompleteConflict + ? Colors.red.shade700 + : Colors.orange.shade700, + borderRadius: + BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + isCompleteConflict + ? Icons.block + : Icons.warning, + size: 14, color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.bold, ), - ), - ], + const SizedBox(width: 4), + Text( + isCompleteConflict + ? 'Indisponible' + : 'Partiellement utilisée', + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ], + ), ), - ), - ], - ), - const SizedBox(height: 4), - Text( - container.name, - style: TextStyle( - color: Colors.grey.shade700, - fontSize: 14, + ], ), - ), - const SizedBox(height: 4), - Row( - children: [ - Icon( - Icons.inventory_2, - size: 14, - color: Colors.blue.shade700, + const SizedBox(height: 4), + Text( + container.name, + style: TextStyle( + color: Colors.grey.shade700, + fontSize: 14, ), - const SizedBox(width: 4), - Text( - '${container.itemCount} équipement(s)', - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.blue, - ), - ), - if (hasConflict) ...[ - const SizedBox(width: 8), + ), + const SizedBox(height: 4), + Row( + children: [ Icon( - Icons.warning, + Icons.inventory_2, size: 14, - color: isCompleteConflict ? Colors.red.shade700 : Colors.orange.shade700, + color: Colors.blue.shade700, ), const SizedBox(width: 4), Text( - conflictInfo.description, - style: TextStyle( - fontSize: 11, - color: isCompleteConflict ? Colors.red.shade700 : Colors.orange.shade700, + '${container.itemCount} équipement(s)', + style: const TextStyle( + fontSize: 13, fontWeight: FontWeight.w500, + color: Colors.blue, ), ), + if (hasConflict) ...[ + const SizedBox(width: 8), + Icon( + Icons.warning, + size: 14, + color: isCompleteConflict + ? Colors.red.shade700 + : Colors.orange.shade700, + ), + const SizedBox(width: 4), + Text( + conflictInfo.description, + style: TextStyle( + fontSize: 11, + color: isCompleteConflict + ? Colors.red.shade700 + : Colors.orange.shade700, + fontWeight: FontWeight.w500, + ), + ), + ], ], - ], + ), + ], + ), + ), + + // Bouton pour déplier/replier + IconButton( + icon: Icon( + isExpanded + ? Icons.expand_less + : Icons.expand_more, + color: AppColors.rouge, + ), + onPressed: () { + if (isExpanded) { + _expandedContainers.remove(container.id); + } else { + _expandedContainers.add(container.id); + } + _selectionChangeNotifier + .value++; // Notifier sans rebuild complet + }, + tooltip: isExpanded ? 'Replier' : 'Voir le contenu', + ), + ], + ), + + // Avertissement pour conteneur complètement indisponible + if (isCompleteConflict && !isSelected) + Container( + margin: const EdgeInsets.only(top: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.red.shade100, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.red.shade300), + ), + child: Row( + children: [ + Icon(Icons.block, + size: 20, color: Colors.red.shade900), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Cette boîte ne peut pas être sélectionnée car tous ses équipements sont déjà utilisés.', + style: TextStyle( + fontSize: 12, + color: Colors.red.shade900, + fontWeight: FontWeight.w500, + ), + ), ), ], ), ), - - // Bouton pour déplier/replier - IconButton( - icon: Icon( - isExpanded ? Icons.expand_less : Icons.expand_more, - color: AppColors.rouge, - ), - onPressed: () { - if (isExpanded) { - _expandedContainers.remove(container.id); - } else { - _expandedContainers.add(container.id); - } - _selectionChangeNotifier.value++; // Notifier sans rebuild complet - }, - tooltip: isExpanded ? 'Replier' : 'Voir le contenu', - ), - ], - ), - - // Avertissement pour conteneur complètement indisponible - if (isCompleteConflict && !isSelected) - Container( - margin: const EdgeInsets.only(top: 12), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.red.shade100, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.red.shade300), - ), - child: Row( - children: [ - Icon(Icons.block, size: 20, color: Colors.red.shade900), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Cette boîte ne peut pas être sélectionnée car tous ses équipements sont déjà utilisés.', - style: TextStyle( - fontSize: 12, - color: Colors.red.shade900, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ), - ], + ], + ), ), ), - ), - // Liste des enfants (si déplié) - if (isExpanded) - _buildContainerChildren(container, conflictInfo), - ], + // Liste des enfants (si déplié) + if (isExpanded) _buildContainerChildren(container, conflictInfo), + ], + ), ), ), - ), ); } /// Widget pour afficher les équipements enfants d'un conteneur - Widget _buildContainerChildren(ContainerModel container, ContainerConflictInfo? conflictInfo) { + Widget _buildContainerChildren( + ContainerModel container, ContainerConflictInfo? conflictInfo) { // Utiliser les équipements paginés et le cache final allEquipment = [..._paginatedEquipments, ..._cachedEquipment]; final childEquipments = allEquipment .where((eq) => container.equipmentIds.contains(eq.id)) .toList(); - DebugLog.info('[EquipmentSelectionDialog] Building container children for ${container.id}: ${container.equipmentIds.length} IDs, found ${childEquipments.length} equipment(s) in cache (total cache: ${_cachedEquipment.length})'); + DebugLog.info( + '[EquipmentSelectionDialog] Building container children for ${container.id}: ${container.equipmentIds.length} IDs, found ${childEquipments.length} equipment(s) in cache (total cache: ${_cachedEquipment.length})'); if (container.equipmentIds.isNotEmpty && childEquipments.isEmpty) { - DebugLog.error('[EquipmentSelectionDialog] Container ${container.id} has ${container.equipmentIds.length} equipment IDs but found 0 equipment in cache!'); - DebugLog.info('[EquipmentSelectionDialog] Looking for IDs: ${container.equipmentIds.take(5).join(", ")}...'); - DebugLog.info('[EquipmentSelectionDialog] Cache contains IDs: ${_cachedEquipment.take(5).map((e) => e.id).join(", ")}...'); + DebugLog.error( + '[EquipmentSelectionDialog] Container ${container.id} has ${container.equipmentIds.length} equipment IDs but found 0 equipment in cache!'); + DebugLog.info( + '[EquipmentSelectionDialog] Looking for IDs: ${container.equipmentIds.take(5).join(", ")}...'); + DebugLog.info( + '[EquipmentSelectionDialog] Cache contains IDs: ${_cachedEquipment.take(5).map((e) => e.id).join(", ")}...'); } if (childEquipments.isEmpty) { @@ -2179,94 +2348,98 @@ class _EquipmentSelectionDialogState extends State { ), const SizedBox(height: 12), ...childEquipments.map((eq) { - final hasConflict = _equipmentConflicts.containsKey(eq.id); - final conflicts = _equipmentConflicts[eq.id] ?? []; + final hasConflict = _equipmentConflicts.containsKey(eq.id); + final conflicts = _equipmentConflicts[eq.id] ?? []; - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: hasConflict ? Colors.orange.shade50 : Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: hasConflict ? Colors.orange.shade300 : Colors.grey.shade300, - ), - ), - child: Row( - children: [ - // Flèche de hiérarchie - Icon( - Icons.subdirectory_arrow_right, - size: 16, - color: Colors.grey.shade600, + return Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: hasConflict ? Colors.orange.shade50 : Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: hasConflict + ? Colors.orange.shade300 + : Colors.grey.shade300, + ), + ), + child: Row( + children: [ + // Flèche de hiérarchie + Icon( + Icons.subdirectory_arrow_right, + size: 16, + color: Colors.grey.shade600, + ), + const SizedBox(width: 8), + + // Icône de l'équipement + eq.category.getIcon(size: 20, color: eq.category.color), + const SizedBox(width: 12), + + // Nom de l'équipement + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + eq.id, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Colors.grey.shade800, ), - const SizedBox(width: 8), - - // Icône de l'équipement - eq.category.getIcon(size: 20, color: eq.category.color), - const SizedBox(width: 12), - - // Nom de l'équipement - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - eq.id, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.grey.shade800, - ), - ), - if (eq.brand != null || eq.model != null) - Text( - '${eq.brand ?? ''} ${eq.model ?? ''}'.trim(), - style: TextStyle( - fontSize: 11, - color: Colors.grey.shade600, - ), - ), - ], + ), + if (eq.brand != null || eq.model != null) + Text( + '${eq.brand ?? ''} ${eq.model ?? ''}'.trim(), + style: TextStyle( + fontSize: 11, + color: Colors.grey.shade600, ), ), + ], + ), + ), - // Indicateur de conflit - if (hasConflict) ...[ - const SizedBox(width: 8), - Tooltip( - message: 'Utilisé sur ${conflicts.length} événement(s)', - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), - decoration: BoxDecoration( - color: Colors.orange.shade700, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.warning, size: 12, color: Colors.white), - const SizedBox(width: 4), - Text( - '${conflicts.length}', - style: const TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ], - ), + // Indicateur de conflit + if (hasConflict) ...[ + const SizedBox(width: 8), + Tooltip( + message: 'Utilisé sur ${conflicts.length} événement(s)', + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 3), + decoration: BoxDecoration( + color: Colors.orange.shade700, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.warning, + size: 12, color: Colors.white), + const SizedBox(width: 4), + Text( + '${conflicts.length}', + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, ), ), ], - ], + ), ), - ); - }), + ), + ], ], ), ); + }), + ], + ), + ); } Widget _buildSelectionPanel() { @@ -2329,7 +2502,6 @@ class _EquipmentSelectionDialogState extends State { ], ), ), - Expanded( child: totalDisplayed == 0 ? const Center( @@ -2352,7 +2524,8 @@ class _EquipmentSelectionDialogState extends State { ), ), ), - ...selectedContainers.map((e) => _buildSelectedContainerTile(e.key, e.value)), + ...selectedContainers.map( + (e) => _buildSelectedContainerTile(e.key, e.value)), ], if (standaloneEquipmentCount > 0) ...[ Padding( @@ -2365,7 +2538,8 @@ class _EquipmentSelectionDialogState extends State { ), ), ), - ...selectedStandaloneEquipment.map((e) => _buildSelectedItemTile(e.key, e.value)), + ...selectedStandaloneEquipment + .map((e) => _buildSelectedItemTile(e.key, e.value)), ], ], ), @@ -2374,7 +2548,6 @@ class _EquipmentSelectionDialogState extends State { ); } - /// Récupère les IDs des équipements d'un conteneur (depuis le cache) List _getContainerEquipmentIds(String containerId) { // On doit récupérer le conteneur depuis le provider de manière synchrone @@ -2422,7 +2595,8 @@ class _EquipmentSelectionDialogState extends State { } else { _expandedContainers.add(id); } - _selectionChangeNotifier.value++; // Notifier sans rebuild complet + _selectionChangeNotifier + .value++; // Notifier sans rebuild complet }, ), IconButton( @@ -2459,7 +2633,8 @@ class _EquipmentSelectionDialogState extends State { style: TextStyle(fontSize: 12, color: Colors.grey.shade700), ), subtitle: item.quantity > 1 - ? Text('Qté: ${item.quantity}', style: const TextStyle(fontSize: 10)) + ? Text('Qté: ${item.quantity}', + style: const TextStyle(fontSize: 10)) : null, // PAS de bouton de suppression pour les enfants ), @@ -2594,7 +2769,8 @@ class _EquipmentSelectionDialogState extends State { style: ElevatedButton.styleFrom( backgroundColor: AppColors.rouge, foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 14), + padding: + const EdgeInsets.symmetric(horizontal: 32, vertical: 14), ), child: const Text('Valider la sélection'), ), @@ -2605,4 +2781,3 @@ class _EquipmentSelectionDialogState extends State { ); } } - diff --git a/em2rp/web/version.json b/em2rp/web/version.json index 40f6533..9fe8860 100644 --- a/em2rp/web/version.json +++ b/em2rp/web/version.json @@ -1,7 +1,7 @@ { - "version": "1.2.7", + "version": "1.2.8", "updateUrl": "https://app.em2events.fr", "forceUpdate": true, - "releaseNotes": "Ajout de la fonctionnalité de génération d'itinéraire avec calcul de coût.", - "timestamp": "2026-06-05T14:03:37.401Z" + "releaseNotes": "Fix bug vue mobile, amélioration de la vue de calcule des frais de déplacement", + "timestamp": "2026-06-05T18:43:05.788Z" } \ No newline at end of file