feat: création branche singletons - début refactorisation
- Sauvegarde des fichiers critiques - Préparation transformation ApiService en singleton - Préparation création CurrentUserService et CurrentAmicaleService - Objectif: renommer Box users -> user
This commit is contained in:
90
.vscode/settings.json
vendored
90
.vscode/settings.json
vendored
@@ -1,90 +0,0 @@
|
|||||||
{
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.defaultFormatter": null,
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.fixAll": "explicit"
|
|
||||||
},
|
|
||||||
"[php]": {
|
|
||||||
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
|
|
||||||
"editor.tabSize": 4,
|
|
||||||
"editor.insertSpaces": true
|
|
||||||
},
|
|
||||||
"[dart]": {
|
|
||||||
"editor.defaultFormatter": "Dart-Code.dart-code",
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnType": true,
|
|
||||||
"editor.rulers": [
|
|
||||||
80
|
|
||||||
],
|
|
||||||
"editor.selectionHighlight": false,
|
|
||||||
"editor.suggest.snippetsPreventQuickSuggestions": false,
|
|
||||||
"editor.suggestSelection": "first",
|
|
||||||
"editor.tabCompletion": "onlySnippets",
|
|
||||||
"editor.wordBasedSuggestions": "off"
|
|
||||||
},
|
|
||||||
"[svelte]": {
|
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
|
||||||
"editor.tabSize": 2
|
|
||||||
},
|
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.tabSize": 2
|
|
||||||
},
|
|
||||||
"[typescript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.tabSize": 2
|
|
||||||
},
|
|
||||||
"[json]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.tabSize": 2
|
|
||||||
},
|
|
||||||
"[markdown]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.wordWrap": "on"
|
|
||||||
},
|
|
||||||
"php.validate.executablePath": "/usr/bin/php",
|
|
||||||
"php.suggest.basic": false,
|
|
||||||
"intelephense.environment.phpVersion": "8.3.0",
|
|
||||||
"dart.flutterSdkPath": "/Users/pierre/dev/flutter",
|
|
||||||
"dart.lineLength": 80,
|
|
||||||
"svelte.enable-ts-plugin": true,
|
|
||||||
"files.exclude": {
|
|
||||||
"**/.git": true,
|
|
||||||
"**/.DS_Store": true,
|
|
||||||
"**/node_modules": true,
|
|
||||||
"**/build": true,
|
|
||||||
"**/.dart_tool": true,
|
|
||||||
"**/.flutter-plugins": true,
|
|
||||||
"**/.flutter-plugins-dependencies": true
|
|
||||||
},
|
|
||||||
"files.associations": {
|
|
||||||
"*.php": "php",
|
|
||||||
"*.dart": "dart",
|
|
||||||
"*.svelte": "svelte"
|
|
||||||
},
|
|
||||||
"search.exclude": {
|
|
||||||
"**/node_modules": true,
|
|
||||||
"**/bower_components": true,
|
|
||||||
"**/build": true,
|
|
||||||
"**/.dart_tool": true,
|
|
||||||
"**/vendor": true
|
|
||||||
},
|
|
||||||
"cline.autoApproveRequests": true,
|
|
||||||
"cline.enableMemoryBank": true,
|
|
||||||
"cline.includeSnippetsFromMemory": true,
|
|
||||||
"cline.contextLength": 10000,
|
|
||||||
"cline.autoFormat": true,
|
|
||||||
"cline.primaryDocumentationFile": "CONTEXT-AI.md",
|
|
||||||
"cline.gitIntegration": true,
|
|
||||||
"cline.projectStructure": {
|
|
||||||
"api": "php",
|
|
||||||
"app": "flutter",
|
|
||||||
"web": "svelte"
|
|
||||||
},
|
|
||||||
"cline.referenceFiles": {
|
|
||||||
"database": "docs/geo_app.dump",
|
|
||||||
"apiEndpoints": "docs/api_endpoints.md",
|
|
||||||
"architecture": "docs/architecture.md"
|
|
||||||
},
|
|
||||||
"cline.databaseSchema": "docs/geo_app.dump"
|
|
||||||
}
|
|
||||||
153
api/.vscode/settings.json
vendored
153
api/.vscode/settings.json
vendored
@@ -1,153 +0,0 @@
|
|||||||
{
|
|
||||||
"window.zoomLevel": 1, // Permet de zoomer, pratique si vous faites une présentation
|
|
||||||
// Apparence
|
|
||||||
// -- Editeur
|
|
||||||
"workbench.startupEditor": "none", // On ne veut pas une page d'accueil chargée
|
|
||||||
"editor.minimap.enabled": true, // On veut voir la minimap
|
|
||||||
"editor.minimap.showSlider": "always", // On veut voir la minimap
|
|
||||||
"editor.minimap.size": "fill", // On veut voir la minimap
|
|
||||||
"editor.minimap.scale": 1,
|
|
||||||
"editor.tokenColorCustomizations": {
|
|
||||||
"textMateRules": [
|
|
||||||
{
|
|
||||||
"scope": [
|
|
||||||
"storage.type.function",
|
|
||||||
"storage.type.class"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"fontStyle": "bold",
|
|
||||||
"foreground": "#4B9CD3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"editor.minimap.renderCharacters": true,
|
|
||||||
"editor.minimap.maxColumn": 120,
|
|
||||||
"breadcrumbs.enabled": false,
|
|
||||||
// -- Tabs
|
|
||||||
"workbench.editor.wrapTabs": true, // On veut voir les tabs
|
|
||||||
"workbench.editor.tabSizing": "shrink", // On veut voir les tabs
|
|
||||||
"workbench.editor.pinnedTabSizing": "compact",
|
|
||||||
"workbench.editor.enablePreview": false, // Un clic sur un fichier l'ouvre
|
|
||||||
// -- Sidebar
|
|
||||||
"workbench.tree.indent": 15, // Indente plus pour plus de clarté dans la sidebar
|
|
||||||
"workbench.tree.renderIndentGuides": "always",
|
|
||||||
// -- Code
|
|
||||||
"editor.occurrencesHighlight": "singleFile", // On veut voir les occurences d'une variable
|
|
||||||
"editor.renderWhitespace": "trailing", // On ne veut pas laisser d'espace en fin de ligne
|
|
||||||
"editor.renderControlCharacters": true, // On veut voir les caractères de contrôle
|
|
||||||
// Thème
|
|
||||||
"editor.fontFamily": "'JetBrains Mono', 'Fira Code', 'Operator Mono Lig', monospace",
|
|
||||||
"editor.fontLigatures": false,
|
|
||||||
"editor.fontSize": 13,
|
|
||||||
"editor.lineHeight": 22,
|
|
||||||
"editor.guides.bracketPairs": "active",
|
|
||||||
// Ergonomie
|
|
||||||
"editor.wordWrap": "off",
|
|
||||||
"editor.rulers": [],
|
|
||||||
"editor.suggest.insertMode": "replace", // L'autocomplétion remplace le mot en cours
|
|
||||||
"editor.acceptSuggestionOnCommitCharacter": false, // Evite que l'autocomplétion soit accepté lors d'un . par exemple
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnPaste": true,
|
|
||||||
"editor.linkedEditing": true, // Quand on change un élément HTML, change la balise fermante
|
|
||||||
"editor.tabSize": 2,
|
|
||||||
"editor.unicodeHighlight.nonBasicASCII": false,
|
|
||||||
"[php]": {
|
|
||||||
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnPaste": true
|
|
||||||
},
|
|
||||||
"intelephense.format.braces": "k&r",
|
|
||||||
"intelephense.format.enable": true,
|
|
||||||
"php.validate.executablePath": "/opt/homebrew/opt/php@8.3/bin/php",
|
|
||||||
"php.executablePath": "/opt/homebrew/opt/php@8.3/bin/php",
|
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnPaste": true
|
|
||||||
},
|
|
||||||
"prettier.printWidth": 360,
|
|
||||||
"prettier.semi": true,
|
|
||||||
"prettier.singleQuote": true,
|
|
||||||
"prettier.tabWidth": 2,
|
|
||||||
"prettier.trailingComma": "es5",
|
|
||||||
"explorer.autoReveal": false,
|
|
||||||
"explorer.confirmDragAndDrop": false,
|
|
||||||
"emmet.triggerExpansionOnTab": true,
|
|
||||||
"emmet.includeLanguages": {
|
|
||||||
"javascript": "javascript",
|
|
||||||
"php": "php",
|
|
||||||
"svelte": "html",
|
|
||||||
"dart": "dart"
|
|
||||||
},
|
|
||||||
"problems.decorations.enabled": true,
|
|
||||||
"explorer.decorations.colors": true,
|
|
||||||
"explorer.decorations.badges": true,
|
|
||||||
"php.validate.enable": true,
|
|
||||||
"php.suggest.basic": false,
|
|
||||||
"dart.analysisExcludedFolders": [],
|
|
||||||
"dart.enableSdkFormatter": true,
|
|
||||||
// Fichiers
|
|
||||||
"files.defaultLanguage": "markdown",
|
|
||||||
"files.autoSaveWorkspaceFilesOnly": true,
|
|
||||||
"files.exclude": {
|
|
||||||
"**/.idea": true
|
|
||||||
},
|
|
||||||
// Languages
|
|
||||||
"javascript.preferences.importModuleSpecifierEnding": "js",
|
|
||||||
"typescript.preferences.importModuleSpecifierEnding": "js",
|
|
||||||
// Extensions
|
|
||||||
"tailwindCSS.experimental.configFile": "web/tailwind.config.js",
|
|
||||||
"editor.quickSuggestions": {
|
|
||||||
"strings": true
|
|
||||||
},
|
|
||||||
"[svelte]": {
|
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
|
||||||
"editor.formatOnSave": true
|
|
||||||
},
|
|
||||||
"prettier.documentSelectors": [
|
|
||||||
"**/*.svelte"
|
|
||||||
],
|
|
||||||
"svelte.plugin.svelte.diagnostics.enable": false,
|
|
||||||
"js/ts.implicitProjectConfig.checkJs": false,
|
|
||||||
"svelte.enable-ts-plugin": false,
|
|
||||||
"cline.autoApproveLimit": 100,
|
|
||||||
"cline.autoApproveRequests": true,
|
|
||||||
"cline.enableMemoryBank": true,
|
|
||||||
"cline.includeSnippetsFromMemory": true,
|
|
||||||
"cline.contextLength": 10000,
|
|
||||||
"cline.autoFormat": true,
|
|
||||||
"cline.primaryDocumentationFile": ".cline",
|
|
||||||
"cline.gitIntegration": true,
|
|
||||||
"cline.projectStructure": {
|
|
||||||
"api": "php",
|
|
||||||
"app": "flutter",
|
|
||||||
"web": "svelte"
|
|
||||||
},
|
|
||||||
"cline.referenceFiles": {
|
|
||||||
"database": "docs/db-resalice.dump",
|
|
||||||
"apiEndpoints": "docs/api_endpoints.md",
|
|
||||||
"architecture": "docs/architecture.md"
|
|
||||||
},
|
|
||||||
"cline.databaseSchema": "docs/db-resalice.dump",
|
|
||||||
"peacock.color": "#dd0531",
|
|
||||||
"workbench.colorCustomizations": {
|
|
||||||
"activityBar.activeBackground": "#fa1b49",
|
|
||||||
"activityBar.background": "#fa1b49",
|
|
||||||
"activityBar.foreground": "#e7e7e7",
|
|
||||||
"activityBar.inactiveForeground": "#e7e7e799",
|
|
||||||
"activityBarBadge.background": "#155e02",
|
|
||||||
"activityBarBadge.foreground": "#e7e7e7",
|
|
||||||
"commandCenter.border": "#e7e7e799",
|
|
||||||
"sash.hoverBorder": "#fa1b49",
|
|
||||||
"statusBar.background": "#dd0531",
|
|
||||||
"statusBar.foreground": "#e7e7e7",
|
|
||||||
"statusBarItem.hoverBackground": "#fa1b49",
|
|
||||||
"statusBarItem.remoteBackground": "#dd0531",
|
|
||||||
"statusBarItem.remoteForeground": "#e7e7e7",
|
|
||||||
"titleBar.activeBackground": "#dd0531",
|
|
||||||
"titleBar.activeForeground": "#e7e7e7",
|
|
||||||
"titleBar.inactiveBackground": "#dd053199",
|
|
||||||
"titleBar.inactiveForeground": "#e7e7e799"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -155,7 +155,8 @@ class LoginController {
|
|||||||
'email' => $email,
|
'email' => $email,
|
||||||
'name' => $decryptedName,
|
'name' => $decryptedName,
|
||||||
'first_name' => $user['first_name'] ?? '',
|
'first_name' => $user['first_name'] ?? '',
|
||||||
'fk_role' => $user['fk_role'] ?? '0'
|
'fk_role' => $user['fk_role'] ?? '0',
|
||||||
|
'fk_entite' => $user['fk_entite'] ?? '0',
|
||||||
// 'interface' supprimée pour se baser uniquement sur le rôle
|
// 'interface' supprimée pour se baser uniquement sur le rôle
|
||||||
];
|
];
|
||||||
Session::login($sessionData);
|
Session::login($sessionData);
|
||||||
@@ -406,7 +407,7 @@ class LoginController {
|
|||||||
// 6. Récupérer les membres (users de l'entité du user) si nécessaire
|
// 6. Récupérer les membres (users de l'entité du user) si nécessaire
|
||||||
if ($interface === 'admin' && $user['fk_role'] == 2 && !empty($user['fk_entite'])) {
|
if ($interface === 'admin' && $user['fk_role'] == 2 && !empty($user['fk_entite'])) {
|
||||||
$membresStmt = $this->db->prepare(
|
$membresStmt = $this->db->prepare(
|
||||||
'SELECT id, fk_role, fk_titre, encrypted_name, first_name, sect_name,
|
'SELECT id, fk_role, fk_entite, fk_titre, encrypted_name, first_name, sect_name,
|
||||||
encrypted_user_name, encrypted_phone, encrypted_mobile, encrypted_email,
|
encrypted_user_name, encrypted_phone, encrypted_mobile, encrypted_email,
|
||||||
date_naissance, date_embauche, chk_active
|
date_naissance, date_embauche, chk_active
|
||||||
FROM users
|
FROM users
|
||||||
@@ -422,6 +423,7 @@ class LoginController {
|
|||||||
$membreItem = [
|
$membreItem = [
|
||||||
'id' => $membre['id'],
|
'id' => $membre['id'],
|
||||||
'fk_role' => $membre['fk_role'],
|
'fk_role' => $membre['fk_role'],
|
||||||
|
'fk_entite' => $membre['fk_entite'],
|
||||||
'fk_titre' => $membre['fk_titre'],
|
'fk_titre' => $membre['fk_titre'],
|
||||||
'first_name' => $membre['first_name'] ?? '',
|
'first_name' => $membre['first_name'] ?? '',
|
||||||
'sect_name' => $membre['sect_name'] ?? '',
|
'sect_name' => $membre['sect_name'] ?? '',
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -24,13 +24,14 @@ class MembreModelAdapter extends TypeAdapter<MembreModel> {
|
|||||||
name: fields[8] as String,
|
name: fields[8] as String,
|
||||||
username: fields[9] as String,
|
username: fields[9] as String,
|
||||||
email: fields[10] as String,
|
email: fields[10] as String,
|
||||||
|
fkEntite: fields[11] as int,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(BinaryWriter writer, MembreModel obj) {
|
void write(BinaryWriter writer, MembreModel obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(11)
|
..writeByte(12)
|
||||||
..writeByte(0)
|
..writeByte(0)
|
||||||
..write(obj.id)
|
..write(obj.id)
|
||||||
..writeByte(1)
|
..writeByte(1)
|
||||||
@@ -52,7 +53,9 @@ class MembreModelAdapter extends TypeAdapter<MembreModel> {
|
|||||||
..writeByte(9)
|
..writeByte(9)
|
||||||
..write(obj.username)
|
..write(obj.username)
|
||||||
..writeByte(10)
|
..writeByte(10)
|
||||||
..write(obj.email);
|
..write(obj.email)
|
||||||
|
..writeByte(11)
|
||||||
|
..write(obj.fkEntite);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
Extension Discovery Cache
|
|
||||||
=========================
|
|
||||||
|
|
||||||
This folder is used by `package:extension_discovery` to cache lists of
|
|
||||||
packages that contains extensions for other packages.
|
|
||||||
|
|
||||||
DO NOT USE THIS FOLDER
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
* Do not read (or rely) the contents of this folder.
|
|
||||||
* Do write to this folder.
|
|
||||||
|
|
||||||
If you're interested in the lists of extensions stored in this folder use the
|
|
||||||
API offered by package `extension_discovery` to get this information.
|
|
||||||
|
|
||||||
If this package doesn't work for your use-case, then don't try to read the
|
|
||||||
contents of this folder. It may change, and will not remain stable.
|
|
||||||
|
|
||||||
Use package `extension_discovery`
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
If you want to access information from this folder.
|
|
||||||
|
|
||||||
Feel free to delete this folder
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Files in this folder act as a cache, and the cache is discarded if the files
|
|
||||||
are older than the modification time of `.dart_tool/package_config.json`.
|
|
||||||
|
|
||||||
Hence, it should never be necessary to clear this cache manually, if you find a
|
|
||||||
need to do please file a bug.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":2,"entries":[{"package":"geosector_app","rootUri":"../","packageUri":"lib/"}]}
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -552,7 +552,6 @@ file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/b
|
|||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/lonlat.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/lonlat.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/utm.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/utm.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/mgrs.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/mgrs.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/nested-1.0.0/lib/nested.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/package_info_plus.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/package_info_plus.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_linux.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_linux.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_web.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_web.dart
|
||||||
@@ -639,19 +638,6 @@ file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projectio
|
|||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/tmerc.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/tmerc.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/utm.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/utm.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/vandg.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/vandg.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/async_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/change_notifier_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/consumer.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/deferred_inherited_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/devtool.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/inherited_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/listenable_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/proxy_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/reassemble_handler.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/selector.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/value_listenable_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/retry-3.1.2/lib/retry.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/retry-3.1.2/lib/retry.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/source_span.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/source_span.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/src/charcode.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/src/charcode.dart
|
||||||
@@ -1801,10 +1787,10 @@ file:///Users/pierre/dev/geosector/app/lib/core/services/location_service.dart
|
|||||||
file:///Users/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
file:///Users/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
file:///Users/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/main.dart
|
file:///Users/pierre/dev/geosector/app/lib/main.dart
|
||||||
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_amicale_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_communication_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_communication_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_home_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_home_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_entite.dart
|
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_history_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_history_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_map_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_map_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_statistics_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_statistics_page.dart
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -552,7 +552,6 @@ file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/b
|
|||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/lonlat.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/lonlat.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/utm.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/classes/utm.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/mgrs.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/mgrs_dart-2.0.0/lib/src/mgrs.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/nested-1.0.0/lib/nested.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/package_info_plus.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/package_info_plus.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_linux.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_linux.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_web.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/package_info_plus-8.3.0/lib/src/package_info_plus_web.dart
|
||||||
@@ -639,19 +638,6 @@ file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projectio
|
|||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/tmerc.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/tmerc.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/utm.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/utm.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/vandg.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/proj4dart-2.1.0/lib/src/projections/vandg.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/async_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/change_notifier_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/consumer.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/deferred_inherited_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/devtool.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/inherited_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/listenable_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/proxy_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/reassemble_handler.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/selector.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/provider-6.1.5/lib/src/value_listenable_provider.dart
|
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/retry-3.1.2/lib/retry.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/retry-3.1.2/lib/retry.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/source_span.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/source_span.dart
|
||||||
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/src/charcode.dart
|
file:///Users/pierre/.pub-cache/hosted/pub.dev/source_span-1.10.1/lib/src/charcode.dart
|
||||||
@@ -1800,10 +1786,10 @@ file:///Users/pierre/dev/geosector/app/lib/core/services/location_service.dart
|
|||||||
file:///Users/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
file:///Users/pierre/dev/geosector/app/lib/core/services/sync_service.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
file:///Users/pierre/dev/geosector/app/lib/core/theme/app_theme.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/main.dart
|
file:///Users/pierre/dev/geosector/app/lib/main.dart
|
||||||
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_amicale_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_communication_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_communication_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_home_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_home_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_dashboard_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_entite.dart
|
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_history_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_history_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_map_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_map_page.dart
|
||||||
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_statistics_page.dart
|
file:///Users/pierre/dev/geosector/app/lib/presentation/admin/admin_statistics_page.dart
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "geosector_app",
|
"name": "geosector_app",
|
||||||
"version": "0.3.2",
|
"version": "0.3.3",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"connectivity_plus",
|
"connectivity_plus",
|
||||||
"cupertino_icons",
|
"cupertino_icons",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
156
app/.vscode/settings.json
vendored
156
app/.vscode/settings.json
vendored
@@ -1,156 +0,0 @@
|
|||||||
{
|
|
||||||
"window.zoomLevel": 1, // Permet de zoomer, pratique si vous faites une présentation
|
|
||||||
// Apparence
|
|
||||||
// -- Editeur
|
|
||||||
"workbench.startupEditor": "none", // On ne veut pas une page d'accueil chargée
|
|
||||||
"editor.minimap.enabled": true, // On veut voir la minimap
|
|
||||||
"editor.minimap.showSlider": "always", // On veut voir la minimap
|
|
||||||
"editor.minimap.size": "fill", // On veut voir la minimap
|
|
||||||
"editor.minimap.scale": 1,
|
|
||||||
"editor.tokenColorCustomizations": {
|
|
||||||
"textMateRules": [
|
|
||||||
{
|
|
||||||
"scope": [
|
|
||||||
"storage.type.function",
|
|
||||||
"storage.type.class"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"fontStyle": "bold",
|
|
||||||
"foreground": "#4B9CD3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"editor.minimap.renderCharacters": true,
|
|
||||||
"editor.minimap.maxColumn": 120,
|
|
||||||
"breadcrumbs.enabled": true,
|
|
||||||
|
|
||||||
// -- Tabs
|
|
||||||
"workbench.editor.wrapTabs": true, // On veut voir les tabs
|
|
||||||
"workbench.editor.tabSizing": "shrink", // On veut voir les tabs
|
|
||||||
"workbench.editor.pinnedTabSizing": "compact",
|
|
||||||
"workbench.editor.enablePreview": false, // Un clic sur un fichier l'ouvre
|
|
||||||
|
|
||||||
// -- Sidebar
|
|
||||||
"workbench.tree.indent": 15, // Indente plus pour plus de clarté dans la sidebar
|
|
||||||
"workbench.tree.renderIndentGuides": "always",
|
|
||||||
// -- Code
|
|
||||||
"editor.occurrencesHighlight": "singleFile", // On veut voir les occurences d'une variable
|
|
||||||
"editor.renderWhitespace": "trailing", // On ne veut pas laisser d'espace en fin de ligne
|
|
||||||
"editor.renderControlCharacters": true, // On veut voir les caractères de contrôle
|
|
||||||
// Thème
|
|
||||||
"editor.fontFamily": "'JetBrains Mono', 'Fira Code', 'Operator Mono Lig', monospace",
|
|
||||||
"editor.fontLigatures": false,
|
|
||||||
"editor.fontSize": 13,
|
|
||||||
"editor.lineHeight": 22,
|
|
||||||
"editor.guides.bracketPairs": "active",
|
|
||||||
// Ergonomie
|
|
||||||
"editor.wordWrap": "off",
|
|
||||||
"editor.rulers": [300],
|
|
||||||
"editor.wordWrapColumn": 300,
|
|
||||||
"editor.suggest.insertMode": "replace", // L'autocomplétion remplace le mot en cours
|
|
||||||
"editor.acceptSuggestionOnCommitCharacter": false, // Evite que l'autocomplétion soit accepté lors d'un . par exemple
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnPaste": true,
|
|
||||||
"editor.linkedEditing": true, // Quand on change un élément HTML, change la balise fermante
|
|
||||||
"editor.tabSize": 2,
|
|
||||||
"editor.unicodeHighlight.nonBasicASCII": false,
|
|
||||||
"[php]": {
|
|
||||||
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client",
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnPaste": true
|
|
||||||
},
|
|
||||||
"intelephense.format.braces": "k&r",
|
|
||||||
"intelephense.format.enable": true,
|
|
||||||
"php.validate.executablePath": "/opt/homebrew/opt/php@8.3/bin/php",
|
|
||||||
"php.executablePath": "/opt/homebrew/opt/php@8.3/bin/php",
|
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnPaste": true
|
|
||||||
},
|
|
||||||
"prettier.printWidth": 360,
|
|
||||||
"prettier.semi": true,
|
|
||||||
"prettier.singleQuote": true,
|
|
||||||
"prettier.tabWidth": 2,
|
|
||||||
"prettier.trailingComma": "es5",
|
|
||||||
"explorer.autoReveal": false,
|
|
||||||
"explorer.confirmDragAndDrop": false,
|
|
||||||
"emmet.triggerExpansionOnTab": true,
|
|
||||||
"emmet.includeLanguages": {
|
|
||||||
"javascript": "javascript",
|
|
||||||
"php": "php",
|
|
||||||
"svelte": "html",
|
|
||||||
"dart": "dart"
|
|
||||||
},
|
|
||||||
"problems.decorations.enabled": true,
|
|
||||||
"explorer.decorations.colors": true,
|
|
||||||
"explorer.decorations.badges": true,
|
|
||||||
"php.validate.enable": true,
|
|
||||||
"php.suggest.basic": false,
|
|
||||||
"dart.analysisExcludedFolders": [],
|
|
||||||
"dart.enableSdkFormatter": true,
|
|
||||||
// Fichiers
|
|
||||||
"files.defaultLanguage": "markdown",
|
|
||||||
"files.autoSaveWorkspaceFilesOnly": true,
|
|
||||||
"files.exclude": {
|
|
||||||
"**/.idea": true
|
|
||||||
},
|
|
||||||
// Languages
|
|
||||||
"javascript.preferences.importModuleSpecifierEnding": "js",
|
|
||||||
"typescript.preferences.importModuleSpecifierEnding": "js",
|
|
||||||
// Extensions
|
|
||||||
"tailwindCSS.experimental.configFile": "web/tailwind.config.js",
|
|
||||||
"editor.quickSuggestions": {
|
|
||||||
"strings": true
|
|
||||||
},
|
|
||||||
"[svelte]": {
|
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
|
||||||
"editor.formatOnSave": true
|
|
||||||
},
|
|
||||||
"prettier.documentSelectors": [
|
|
||||||
"**/*.svelte"
|
|
||||||
],
|
|
||||||
"svelte.plugin.svelte.diagnostics.enable": false,
|
|
||||||
"js/ts.implicitProjectConfig.checkJs": false,
|
|
||||||
"svelte.enable-ts-plugin": false,
|
|
||||||
"cline.autoApproveLimit": 100,
|
|
||||||
"cline.autoApproveRequests": true,
|
|
||||||
"cline.enableMemoryBank": true,
|
|
||||||
"cline.includeSnippetsFromMemory": true,
|
|
||||||
"cline.contextLength": 10000,
|
|
||||||
"cline.autoFormat": true,
|
|
||||||
"cline.primaryDocumentationFile": ".cline",
|
|
||||||
"cline.gitIntegration": true,
|
|
||||||
"cline.projectStructure": {
|
|
||||||
"api": "php",
|
|
||||||
"app": "flutter",
|
|
||||||
"web": "svelte"
|
|
||||||
},
|
|
||||||
"cline.referenceFiles": {
|
|
||||||
"database": "docs/db-resalice.dump",
|
|
||||||
"apiEndpoints": "docs/api_endpoints.md",
|
|
||||||
"architecture": "docs/architecture.md"
|
|
||||||
},
|
|
||||||
"cline.databaseSchema": "docs/db-resalice.dump",
|
|
||||||
"peacock.color": "#42b883",
|
|
||||||
"workbench.colorCustomizations": {
|
|
||||||
"activityBar.activeBackground": "#65c89b",
|
|
||||||
"activityBar.background": "#65c89b",
|
|
||||||
"activityBar.foreground": "#15202b",
|
|
||||||
"activityBar.inactiveForeground": "#15202b99",
|
|
||||||
"activityBarBadge.background": "#945bc4",
|
|
||||||
"activityBarBadge.foreground": "#e7e7e7",
|
|
||||||
"commandCenter.border": "#15202b99",
|
|
||||||
"sash.hoverBorder": "#65c89b",
|
|
||||||
"statusBar.background": "#42b883",
|
|
||||||
"statusBar.foreground": "#15202b",
|
|
||||||
"statusBarItem.hoverBackground": "#359268",
|
|
||||||
"statusBarItem.remoteBackground": "#42b883",
|
|
||||||
"statusBarItem.remoteForeground": "#15202b",
|
|
||||||
"titleBar.activeBackground": "#42b883",
|
|
||||||
"titleBar.activeForeground": "#15202b",
|
|
||||||
"titleBar.inactiveBackground": "#42b88399",
|
|
||||||
"titleBar.inactiveForeground": "#15202b99"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1537
app/PLAN2-APP.md
Normal file
1537
app/PLAN2-APP.md
Normal file
File diff suppressed because it is too large
Load Diff
2853
app/README2-APP.md
Normal file
2853
app/README2-APP.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,9 @@ class MembreModel extends HiveObject {
|
|||||||
@HiveField(10)
|
@HiveField(10)
|
||||||
final String email;
|
final String email;
|
||||||
|
|
||||||
|
@HiveField(11)
|
||||||
|
final int fkEntite;
|
||||||
|
|
||||||
MembreModel({
|
MembreModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.fkRole,
|
required this.fkRole,
|
||||||
@@ -49,6 +52,7 @@ class MembreModel extends HiveObject {
|
|||||||
required this.name,
|
required this.name,
|
||||||
required this.username,
|
required this.username,
|
||||||
required this.email,
|
required this.email,
|
||||||
|
required this.fkEntite,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Factory pour convertir depuis JSON (API)
|
// Factory pour convertir depuis JSON (API)
|
||||||
@@ -63,13 +67,15 @@ class MembreModel extends HiveObject {
|
|||||||
|
|
||||||
// Convertir le titre en int, qu'il soit déjà int ou string
|
// Convertir le titre en int, qu'il soit déjà int ou string
|
||||||
final dynamic rawTitre = json['fk_titre'];
|
final dynamic rawTitre = json['fk_titre'];
|
||||||
final int fkTitre =
|
final int fkTitre = rawTitre is String ? int.parse(rawTitre) : rawTitre as int;
|
||||||
rawTitre is String ? int.parse(rawTitre) : rawTitre as int;
|
|
||||||
|
|
||||||
// Convertir le chkActive en int, qu'il soit déjà int ou string
|
// Convertir le chkActive en int, qu'il soit déjà int ou string
|
||||||
final dynamic rawActive = json['chk_active'];
|
final dynamic rawActive = json['chk_active'];
|
||||||
final int chkActive =
|
final int chkActive = rawActive is String ? int.parse(rawActive) : rawActive as int;
|
||||||
rawActive is String ? int.parse(rawActive) : rawActive as int;
|
|
||||||
|
// Convertir le fkEntite en int, qu'il soit déjà int ou string
|
||||||
|
final dynamic rawEntite = json['fk_entite'];
|
||||||
|
final int fkEntite = rawEntite is String ? int.parse(rawEntite) : rawEntite as int;
|
||||||
|
|
||||||
return MembreModel(
|
return MembreModel(
|
||||||
id: id,
|
id: id,
|
||||||
@@ -77,16 +83,13 @@ class MembreModel extends HiveObject {
|
|||||||
fkTitre: fkTitre,
|
fkTitre: fkTitre,
|
||||||
firstName: json['first_name'] ?? '',
|
firstName: json['first_name'] ?? '',
|
||||||
sectName: json['sect_name'],
|
sectName: json['sect_name'],
|
||||||
dateNaissance: json['date_naissance'] != null
|
dateNaissance: json['date_naissance'] != null ? DateTime.parse(json['date_naissance']) : null,
|
||||||
? DateTime.parse(json['date_naissance'])
|
dateEmbauche: json['date_embauche'] != null ? DateTime.parse(json['date_embauche']) : null,
|
||||||
: null,
|
|
||||||
dateEmbauche: json['date_embauche'] != null
|
|
||||||
? DateTime.parse(json['date_embauche'])
|
|
||||||
: null,
|
|
||||||
chkActive: chkActive,
|
chkActive: chkActive,
|
||||||
name: json['name'] ?? '',
|
name: json['name'] ?? '',
|
||||||
username: json['username'] ?? '',
|
username: json['username'] ?? '',
|
||||||
email: json['email'] ?? '',
|
email: json['email'] ?? '',
|
||||||
|
fkEntite: fkEntite,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +107,7 @@ class MembreModel extends HiveObject {
|
|||||||
'name': name,
|
'name': name,
|
||||||
'username': username,
|
'username': username,
|
||||||
'email': email,
|
'email': email,
|
||||||
|
'fk_entite': fkEntite,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +123,10 @@ class MembreModel extends HiveObject {
|
|||||||
String? name,
|
String? name,
|
||||||
String? username,
|
String? username,
|
||||||
String? email,
|
String? email,
|
||||||
|
int? fkEntite,
|
||||||
}) {
|
}) {
|
||||||
return MembreModel(
|
return MembreModel(
|
||||||
id: this.id,
|
id: id,
|
||||||
fkRole: fkRole ?? this.fkRole,
|
fkRole: fkRole ?? this.fkRole,
|
||||||
fkTitre: fkTitre ?? this.fkTitre,
|
fkTitre: fkTitre ?? this.fkTitre,
|
||||||
firstName: firstName ?? this.firstName,
|
firstName: firstName ?? this.firstName,
|
||||||
@@ -132,6 +137,7 @@ class MembreModel extends HiveObject {
|
|||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
username: username ?? this.username,
|
username: username ?? this.username,
|
||||||
email: email ?? this.email,
|
email: email ?? this.email,
|
||||||
|
fkEntite: fkEntite ?? this.fkEntite,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,14 @@ class MembreModelAdapter extends TypeAdapter<MembreModel> {
|
|||||||
name: fields[8] as String,
|
name: fields[8] as String,
|
||||||
username: fields[9] as String,
|
username: fields[9] as String,
|
||||||
email: fields[10] as String,
|
email: fields[10] as String,
|
||||||
|
fkEntite: fields[11] as int,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(BinaryWriter writer, MembreModel obj) {
|
void write(BinaryWriter writer, MembreModel obj) {
|
||||||
writer
|
writer
|
||||||
..writeByte(11)
|
..writeByte(12)
|
||||||
..writeByte(0)
|
..writeByte(0)
|
||||||
..write(obj.id)
|
..write(obj.id)
|
||||||
..writeByte(1)
|
..writeByte(1)
|
||||||
@@ -56,7 +57,9 @@ class MembreModelAdapter extends TypeAdapter<MembreModel> {
|
|||||||
..writeByte(9)
|
..writeByte(9)
|
||||||
..write(obj.username)
|
..write(obj.username)
|
||||||
..writeByte(10)
|
..writeByte(10)
|
||||||
..write(obj.email);
|
..write(obj.email)
|
||||||
|
..writeByte(11)
|
||||||
|
..write(obj.fkEntite);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import 'package:geosector_app/core/data/models/amicale_model.dart';
|
|||||||
|
|
||||||
class AmicaleRepository extends ChangeNotifier {
|
class AmicaleRepository extends ChangeNotifier {
|
||||||
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||||
Box<AmicaleModel> get _amicaleBox =>
|
Box<AmicaleModel> get _amicaleBox => Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
|
||||||
Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
|
|
||||||
|
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
@@ -18,6 +17,19 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
// Getters
|
// Getters
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
|
|
||||||
|
// Méthode pour exposer la Box Hive (nécessaire pour ValueListenableBuilder)
|
||||||
|
Box<AmicaleModel> getAmicalesBox() {
|
||||||
|
try {
|
||||||
|
if (!Hive.isBoxOpen(AppKeys.amicaleBoxName)) {
|
||||||
|
throw Exception('La boîte amicales n\'est pas ouverte');
|
||||||
|
}
|
||||||
|
return _amicaleBox;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors de l\'accès à la boîte amicales: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
|
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
|
||||||
Future<void> _ensureBoxIsOpen() async {
|
Future<void> _ensureBoxIsOpen() async {
|
||||||
try {
|
try {
|
||||||
@@ -59,8 +71,7 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
_ensureBoxIsOpen();
|
_ensureBoxIsOpen();
|
||||||
return _amicaleBox.get(fkEntite);
|
return _amicaleBox.get(fkEntite);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de la récupération de l\'amicale de l\'utilisateur: $e');
|
||||||
'Erreur lors de la récupération de l\'amicale de l\'utilisateur: $e');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,8 +157,7 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
final amicale = AmicaleModel.fromJson(amicaleMap);
|
final amicale = AmicaleModel.fromJson(amicaleMap);
|
||||||
await _amicaleBox.put(amicale.id, amicale);
|
await _amicaleBox.put(amicale.id, amicale);
|
||||||
count++;
|
count++;
|
||||||
debugPrint(
|
debugPrint('Amicale unique traitée: ${amicale.name} (ID: ${amicale.id})');
|
||||||
'Amicale unique traitée: ${amicale.name} (ID: ${amicale.id})');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Erreur lors du traitement de l\'amicale unique: $e');
|
debugPrint('Erreur lors du traitement de l\'amicale unique: $e');
|
||||||
debugPrint('Exception détaillée: $e');
|
debugPrint('Exception détaillée: $e');
|
||||||
@@ -177,8 +187,7 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
await processAmicalesData(amicalesData);
|
await processAmicalesData(amicalesData);
|
||||||
return getAllAmicales();
|
return getAllAmicales();
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de la récupération des amicales: ${response.statusCode}');
|
||||||
'Erreur lors de la récupération des amicales: ${response.statusCode}');
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -204,8 +213,7 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
await saveAmicale(amicale);
|
await saveAmicale(amicale);
|
||||||
return amicale;
|
return amicale;
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de la récupération de l\'amicale: ${response.statusCode}');
|
||||||
'Erreur lors de la récupération de l\'amicale: ${response.statusCode}');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -234,8 +242,7 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
await saveAmicale(updatedAmicale);
|
await saveAmicale(updatedAmicale);
|
||||||
return updatedAmicale;
|
return updatedAmicale;
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de la mise à jour de l\'amicale: ${response.statusCode}');
|
||||||
'Erreur lors de la mise à jour de l\'amicale: ${response.statusCode}');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -254,23 +261,17 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final lowercaseQuery = query.toLowerCase();
|
final lowercaseQuery = query.toLowerCase();
|
||||||
return _amicaleBox.values
|
return _amicaleBox.values.where((amicale) => amicale.name.toLowerCase().contains(lowercaseQuery)).toList();
|
||||||
.where((amicale) => amicale.name.toLowerCase().contains(lowercaseQuery))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtrer les amicales par type
|
// Filtrer les amicales par type
|
||||||
List<AmicaleModel> getAmicalesByType(int type) {
|
List<AmicaleModel> getAmicalesByType(int type) {
|
||||||
return _amicaleBox.values
|
return _amicaleBox.values.where((amicale) => amicale.fkType == type).toList();
|
||||||
.where((amicale) => amicale.fkType == type)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtrer les amicales par région
|
// Filtrer les amicales par région
|
||||||
List<AmicaleModel> getAmicalesByRegion(int regionId) {
|
List<AmicaleModel> getAmicalesByRegion(int regionId) {
|
||||||
return _amicaleBox.values
|
return _amicaleBox.values.where((amicale) => amicale.fkRegion == regionId).toList();
|
||||||
.where((amicale) => amicale.fkRegion == regionId)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtrer les amicales actives
|
// Filtrer les amicales actives
|
||||||
@@ -280,16 +281,12 @@ class AmicaleRepository extends ChangeNotifier {
|
|||||||
|
|
||||||
// Filtrer les amicales par code postal
|
// Filtrer les amicales par code postal
|
||||||
List<AmicaleModel> getAmicalesByPostalCode(String postalCode) {
|
List<AmicaleModel> getAmicalesByPostalCode(String postalCode) {
|
||||||
return _amicaleBox.values
|
return _amicaleBox.values.where((amicale) => amicale.codePostal == postalCode).toList();
|
||||||
.where((amicale) => amicale.codePostal == postalCode)
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtrer les amicales par ville
|
// Filtrer les amicales par ville
|
||||||
List<AmicaleModel> getAmicalesByCity(String city) {
|
List<AmicaleModel> getAmicalesByCity(String city) {
|
||||||
final lowercaseCity = city.toLowerCase();
|
final lowercaseCity = city.toLowerCase();
|
||||||
return _amicaleBox.values
|
return _amicaleBox.values.where((amicale) => amicale.ville.toLowerCase().contains(lowercaseCity)).toList();
|
||||||
.where((amicale) => amicale.ville.toLowerCase().contains(lowercaseCity))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import 'package:geosector_app/core/data/models/membre_model.dart';
|
|||||||
|
|
||||||
class MembreRepository extends ChangeNotifier {
|
class MembreRepository extends ChangeNotifier {
|
||||||
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
|
||||||
Box<MembreModel> get _membreBox =>
|
Box<MembreModel> get _membreBox => Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||||
Hive.box<MembreModel>(AppKeys.membresBoxName);
|
|
||||||
|
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
@@ -20,6 +19,19 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
List<MembreModel> get membres => getAllMembres();
|
List<MembreModel> get membres => getAllMembres();
|
||||||
|
|
||||||
|
// Méthode pour exposer la Box Hive (nécessaire pour ValueListenableBuilder)
|
||||||
|
Box<MembreModel> getMembresBox() {
|
||||||
|
try {
|
||||||
|
if (!Hive.isBoxOpen(AppKeys.membresBoxName)) {
|
||||||
|
throw Exception('La boîte membres n\'est pas ouverte');
|
||||||
|
}
|
||||||
|
return Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors de l\'accès à la boîte membres: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
|
// Méthode pour vérifier si une boîte est ouverte et l'ouvrir si nécessaire
|
||||||
Future<void> _ensureBoxIsOpen() async {
|
Future<void> _ensureBoxIsOpen() async {
|
||||||
try {
|
try {
|
||||||
@@ -29,10 +41,37 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
debugPrint('Boîte ${AppKeys.membresBoxName} ouverte avec succès');
|
debugPrint('Boîte ${AppKeys.membresBoxName} ouverte avec succès');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de l\'ouverture de la boîte ${AppKeys.membresBoxName}: $e');
|
||||||
'Erreur lors de l\'ouverture de la boîte ${AppKeys.membresBoxName}: $e');
|
throw Exception('Impossible d\'ouvrir la boîte ${AppKeys.membresBoxName}: $e');
|
||||||
throw Exception(
|
}
|
||||||
'Impossible d\'ouvrir la boîte ${AppKeys.membresBoxName}: $e');
|
}
|
||||||
|
|
||||||
|
List<MembreModel> getMembresByAmicale(int fkEntite) {
|
||||||
|
try {
|
||||||
|
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite).toList();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors de la récupération des membres par amicale: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les membres actifs par amicale
|
||||||
|
List<MembreModel> getActiveMembresByAmicale(int fkEntite) {
|
||||||
|
try {
|
||||||
|
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite && membre.chkActive == 1).toList();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors de la récupération des membres actifs par amicale: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compter les membres par amicale
|
||||||
|
int countMembresByAmicale(int fkEntite) {
|
||||||
|
try {
|
||||||
|
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite).length;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Erreur lors du comptage des membres par amicale: $e');
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,8 +118,7 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
final hasConnection = await _apiService.hasInternetConnection();
|
||||||
if (!hasConnection) {
|
if (!hasConnection) {
|
||||||
debugPrint(
|
debugPrint('Pas de connexion Internet, utilisation des données locales');
|
||||||
'Pas de connexion Internet, utilisation des données locales');
|
|
||||||
return getAllMembres();
|
return getAllMembres();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,8 +145,7 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
return membres;
|
return membres;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(
|
debugPrint('Erreur lors de la récupération des membres depuis l\'API: $e');
|
||||||
'Erreur lors de la récupération des membres depuis l\'API: $e');
|
|
||||||
return getAllMembres();
|
return getAllMembres();
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
@@ -129,8 +166,7 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint à adapter selon votre API
|
// Endpoint à adapter selon votre API
|
||||||
final response =
|
final response = await _apiService.post('/membres', data: membre.toJson());
|
||||||
await _apiService.post('/membres', data: membre.toJson());
|
|
||||||
final membreData = response.data['membre'];
|
final membreData = response.data['membre'];
|
||||||
|
|
||||||
final newMembre = MembreModel.fromJson(membreData);
|
final newMembre = MembreModel.fromJson(membreData);
|
||||||
@@ -154,14 +190,12 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
final hasConnection = await _apiService.hasInternetConnection();
|
||||||
if (!hasConnection) {
|
if (!hasConnection) {
|
||||||
debugPrint(
|
debugPrint('Pas de connexion Internet, impossible de mettre à jour le membre');
|
||||||
'Pas de connexion Internet, impossible de mettre à jour le membre');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Endpoint à adapter selon votre API
|
// Endpoint à adapter selon votre API
|
||||||
final response =
|
final response = await _apiService.put('/membres/${membre.id}', data: membre.toJson());
|
||||||
await _apiService.put('/membres/${membre.id}', data: membre.toJson());
|
|
||||||
final membreData = response.data['membre'];
|
final membreData = response.data['membre'];
|
||||||
|
|
||||||
final updatedMembre = MembreModel.fromJson(membreData);
|
final updatedMembre = MembreModel.fromJson(membreData);
|
||||||
@@ -185,8 +219,7 @@ class MembreRepository extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
final hasConnection = await _apiService.hasInternetConnection();
|
final hasConnection = await _apiService.hasInternetConnection();
|
||||||
if (!hasConnection) {
|
if (!hasConnection) {
|
||||||
debugPrint(
|
debugPrint('Pas de connexion Internet, impossible de supprimer le membre');
|
||||||
'Pas de connexion Internet, impossible de supprimer le membre');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,9 +203,7 @@ class ApiService {
|
|||||||
retryIf: (e) => e is SocketException || e is TimeoutException,
|
retryIf: (e) => e is SocketException || e is TimeoutException,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (response.data as List)
|
return (response.data as List).map((json) => UserModel.fromJson(json)).toList();
|
||||||
.map((json) => UserModel.fromJson(json))
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Gérer les erreurs
|
// Gérer les erreurs
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
# Structure de présentation
|
|
||||||
|
|
||||||
Ce dossier contient tous les éléments liés à l'interface utilisateur de l'application, organisés comme suit :
|
|
||||||
|
|
||||||
## Sous-dossiers
|
|
||||||
|
|
||||||
- `/admin` : Pages et widgets spécifiques à l'interface administrateur
|
|
||||||
- `/user` : Pages et widgets spécifiques à l'interface utilisateur
|
|
||||||
- `/auth` : Pages et widgets liés à l'authentification
|
|
||||||
- `/public` : Pages et widgets accessibles sans authentification
|
|
||||||
- `/widgets` : Widgets partagés utilisés dans plusieurs parties de l'application
|
|
||||||
|
|
||||||
## Organisation des fichiers
|
|
||||||
|
|
||||||
Chaque sous-dossier peut contenir :
|
|
||||||
- Des pages (écrans complets)
|
|
||||||
- Des widgets spécifiques à cette section
|
|
||||||
- Des modèles de données d'UI
|
|
||||||
- Des utilitaires d'UI spécifiques
|
|
||||||
|
|
||||||
## Bonnes pratiques
|
|
||||||
|
|
||||||
- Les widgets réutilisables dans plusieurs sections doivent être placés dans `/widgets`
|
|
||||||
- Les widgets spécifiques à une section doivent être placés dans le sous-dossier correspondant
|
|
||||||
- Utiliser des imports relatifs pour les fichiers du même module
|
|
||||||
- Utiliser des imports absolus pour les fichiers d'autres modules
|
|
||||||
370
app/lib/presentation/admin/admin_amicale_page.dart
Normal file
370
app/lib/presentation/admin/admin_amicale_page.dart
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||||
|
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||||
|
import 'package:geosector_app/core/data/models/user_model.dart';
|
||||||
|
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||||
|
import 'package:geosector_app/core/repositories/amicale_repository.dart';
|
||||||
|
import 'package:geosector_app/core/repositories/membre_repository.dart';
|
||||||
|
import 'package:geosector_app/presentation/widgets/amicale_table_widget.dart';
|
||||||
|
import 'package:geosector_app/presentation/widgets/membre_table_widget.dart';
|
||||||
|
|
||||||
|
/// Class pour dessiner les petits points blancs sur le fond
|
||||||
|
class DotsPainter extends CustomPainter {
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.5)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
final random = math.Random(42); // Seed fixe pour consistance
|
||||||
|
final numberOfDots = (size.width * size.height) ~/ 1500;
|
||||||
|
|
||||||
|
for (int i = 0; i < numberOfDots; i++) {
|
||||||
|
final x = random.nextDouble() * size.width;
|
||||||
|
final y = random.nextDouble() * size.height;
|
||||||
|
final radius = 1.0 + random.nextDouble() * 2.0;
|
||||||
|
canvas.drawCircle(Offset(x, y), radius, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Page d'administration de l'amicale et des membres
|
||||||
|
/// Cette page est intégrée dans le tableau de bord administrateur
|
||||||
|
class AdminAmicalePage extends StatefulWidget {
|
||||||
|
final UserRepository userRepository;
|
||||||
|
final AmicaleRepository amicaleRepository;
|
||||||
|
final MembreRepository membreRepository;
|
||||||
|
|
||||||
|
const AdminAmicalePage({
|
||||||
|
super.key,
|
||||||
|
required this.userRepository,
|
||||||
|
required this.amicaleRepository,
|
||||||
|
required this.membreRepository,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AdminAmicalePage> createState() => _AdminAmicalePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AdminAmicalePageState extends State<AdminAmicalePage> {
|
||||||
|
UserModel? _currentUser;
|
||||||
|
String? _errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadCurrentUser() {
|
||||||
|
final currentUser = widget.userRepository.getCurrentUser();
|
||||||
|
|
||||||
|
if (currentUser == null) {
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = 'Utilisateur non connecté';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUser.fkEntite == null) {
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = 'Utilisateur non associé à une amicale';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_currentUser = currentUser;
|
||||||
|
_errorMessage = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleEditAmicale(AmicaleModel amicale) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Modifier l\'amicale'),
|
||||||
|
content: Text('Voulez-vous modifier l\'amicale ${amicale.name} ?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Annuler'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// TODO: Naviguer vers la page de modification
|
||||||
|
// Navigator.of(context).push(
|
||||||
|
// MaterialPageRoute(
|
||||||
|
// builder: (context) => EditAmicalePage(
|
||||||
|
// amicale: amicale,
|
||||||
|
// amicaleRepository: widget.amicaleRepository,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
child: const Text('Modifier'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleEditMembre(MembreModel membre) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Modifier le membre'),
|
||||||
|
content: Text('Voulez-vous modifier le membre ${membre.firstName} ${membre.name} ?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Annuler'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// TODO: Naviguer vers la page de modification
|
||||||
|
// Navigator.of(context).push(
|
||||||
|
// MaterialPageRoute(
|
||||||
|
// builder: (context) => EditMembrePage(
|
||||||
|
// membre: membre,
|
||||||
|
// membreRepository: widget.membreRepository,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
child: const Text('Modifier'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleAddMembre() {
|
||||||
|
if (_currentUser?.fkEntite == null) return;
|
||||||
|
|
||||||
|
// TODO: Naviguer vers la page d'ajout de membre
|
||||||
|
// Navigator.of(context).push(
|
||||||
|
// MaterialPageRoute(
|
||||||
|
// builder: (context) => AddMembrePage(
|
||||||
|
// amicaleId: _currentUser!.fkEntite!,
|
||||||
|
// membreRepository: widget.membreRepository,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
// Fond dégradé avec petits points blancs
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [Colors.white, Colors.red.shade300],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: DotsPainter(),
|
||||||
|
child: const SizedBox(width: double.infinity, height: double.infinity),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Contenu principal
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Titre de la page
|
||||||
|
Text(
|
||||||
|
'Mon amicale et ses membres',
|
||||||
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Message d'erreur si présent
|
||||||
|
if (_errorMessage != null)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.red.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.error_outline, color: Colors.red),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
_errorMessage!,
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Contenu principal avec ValueListenableBuilder
|
||||||
|
if (_currentUser != null && _currentUser!.fkEntite != null)
|
||||||
|
Expanded(
|
||||||
|
child: ValueListenableBuilder<Box<AmicaleModel>>(
|
||||||
|
valueListenable: widget.amicaleRepository.getAmicalesBox().listenable(),
|
||||||
|
builder: (context, amicalesBox, child) {
|
||||||
|
final amicale = amicalesBox.get(_currentUser!.fkEntite!);
|
||||||
|
|
||||||
|
if (amicale == null) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.business_outlined,
|
||||||
|
size: 64,
|
||||||
|
color: theme.colorScheme.primary.withOpacity(0.7),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Amicale non trouvée',
|
||||||
|
style: theme.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'L\'amicale associée à votre compte n\'existe plus.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ValueListenableBuilder<Box<MembreModel>>(
|
||||||
|
valueListenable: widget.membreRepository.getMembresBox().listenable(),
|
||||||
|
builder: (context, membresBox, child) {
|
||||||
|
// Filtrer les membres par amicale
|
||||||
|
// Note: Il faudra ajouter le champ fkEntite au modèle MembreModel
|
||||||
|
final membres = membresBox.values.where((membre) => membre.fkEntite == _currentUser!.fkEntite).toList();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Section Amicale
|
||||||
|
Text(
|
||||||
|
'Informations de l\'amicale',
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Tableau Amicale
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: AmicaleTableWidget(
|
||||||
|
amicales: [amicale],
|
||||||
|
onEdit: null,
|
||||||
|
onDelete: null,
|
||||||
|
amicaleRepository: widget.amicaleRepository,
|
||||||
|
userRepository: widget.userRepository,
|
||||||
|
apiService: null, // Ou passez l'ApiService si vous l'avez disponible
|
||||||
|
showActionsColumn: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// Section Membres
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Membres de l\'amicale (${membres.length})',
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: _handleAddMembre,
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Ajouter un membre'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: theme.colorScheme.primary,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Tableau Membres
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.05),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: MembreTableWidget(
|
||||||
|
membres: membres,
|
||||||
|
onEdit: _handleEditMembre,
|
||||||
|
onDelete: null, // Géré par l'admin principal
|
||||||
|
membreRepository: widget.membreRepository,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Message si pas d'utilisateur connecté
|
||||||
|
if (_currentUser == null)
|
||||||
|
const Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import 'admin_statistics_page.dart';
|
|||||||
import 'admin_history_page.dart';
|
import 'admin_history_page.dart';
|
||||||
import 'admin_communication_page.dart';
|
import 'admin_communication_page.dart';
|
||||||
import 'admin_map_page.dart';
|
import 'admin_map_page.dart';
|
||||||
import 'admin_entite.dart';
|
import 'admin_amicale_page.dart';
|
||||||
|
|
||||||
/// Class pour dessiner les petits points blancs sur le fond
|
/// Class pour dessiner les petits points blancs sur le fond
|
||||||
class DotsPainter extends CustomPainter {
|
class DotsPainter extends CustomPainter {
|
||||||
@@ -37,14 +37,13 @@ class DotsPainter extends CustomPainter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AdminDashboardPage extends StatefulWidget {
|
class AdminDashboardPage extends StatefulWidget {
|
||||||
const AdminDashboardPage({Key? key}) : super(key: key);
|
const AdminDashboardPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AdminDashboardPage> createState() => _AdminDashboardPageState();
|
State<AdminDashboardPage> createState() => _AdminDashboardPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AdminDashboardPageState extends State<AdminDashboardPage>
|
class _AdminDashboardPageState extends State<AdminDashboardPage> with WidgetsBindingObserver {
|
||||||
with WidgetsBindingObserver {
|
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
// Liste des pages à afficher
|
// Liste des pages à afficher
|
||||||
@@ -59,31 +58,31 @@ class _AdminDashboardPageState extends State<AdminDashboardPage>
|
|||||||
label: 'Tableau de bord',
|
label: 'Tableau de bord',
|
||||||
icon: Icons.dashboard_outlined,
|
icon: Icons.dashboard_outlined,
|
||||||
selectedIcon: Icons.dashboard,
|
selectedIcon: Icons.dashboard,
|
||||||
page: AdminDashboardHomePage(),
|
pageType: _PageType.dashboardHome,
|
||||||
),
|
),
|
||||||
const _NavigationItem(
|
const _NavigationItem(
|
||||||
label: 'Statistiques',
|
label: 'Statistiques',
|
||||||
icon: Icons.bar_chart_outlined,
|
icon: Icons.bar_chart_outlined,
|
||||||
selectedIcon: Icons.bar_chart,
|
selectedIcon: Icons.bar_chart,
|
||||||
page: AdminStatisticsPage(),
|
pageType: _PageType.statistics,
|
||||||
),
|
),
|
||||||
const _NavigationItem(
|
const _NavigationItem(
|
||||||
label: 'Historique',
|
label: 'Historique',
|
||||||
icon: Icons.history_outlined,
|
icon: Icons.history_outlined,
|
||||||
selectedIcon: Icons.history,
|
selectedIcon: Icons.history,
|
||||||
page: AdminHistoryPage(),
|
pageType: _PageType.history,
|
||||||
),
|
),
|
||||||
const _NavigationItem(
|
const _NavigationItem(
|
||||||
label: 'Messages',
|
label: 'Messages',
|
||||||
icon: Icons.chat_outlined,
|
icon: Icons.chat_outlined,
|
||||||
selectedIcon: Icons.chat,
|
selectedIcon: Icons.chat,
|
||||||
page: AdminCommunicationPage(),
|
pageType: _PageType.communication,
|
||||||
),
|
),
|
||||||
const _NavigationItem(
|
const _NavigationItem(
|
||||||
label: 'Carte',
|
label: 'Carte',
|
||||||
icon: Icons.map_outlined,
|
icon: Icons.map_outlined,
|
||||||
selectedIcon: Icons.map,
|
selectedIcon: Icons.map,
|
||||||
page: AdminMapPage(),
|
pageType: _PageType.map,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -93,18 +92,42 @@ class _AdminDashboardPageState extends State<AdminDashboardPage>
|
|||||||
label: 'Amicale & membres',
|
label: 'Amicale & membres',
|
||||||
icon: Icons.business_outlined,
|
icon: Icons.business_outlined,
|
||||||
selectedIcon: Icons.business,
|
selectedIcon: Icons.business,
|
||||||
page: AdminEntitePage(),
|
pageType: _PageType.amicale,
|
||||||
requiredRole: 2,
|
requiredRole: 2,
|
||||||
),
|
),
|
||||||
const _NavigationItem(
|
const _NavigationItem(
|
||||||
label: 'Opérations',
|
label: 'Opérations',
|
||||||
icon: Icons.calendar_today_outlined,
|
icon: Icons.calendar_today_outlined,
|
||||||
selectedIcon: Icons.calendar_today,
|
selectedIcon: Icons.calendar_today,
|
||||||
page: Scaffold(body: Center(child: Text('Page Opérations'))),
|
pageType: _PageType.operations,
|
||||||
requiredRole: 2,
|
requiredRole: 2,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Construire la page basée sur le type
|
||||||
|
Widget _buildPage(_PageType pageType) {
|
||||||
|
switch (pageType) {
|
||||||
|
case _PageType.dashboardHome:
|
||||||
|
return const AdminDashboardHomePage();
|
||||||
|
case _PageType.statistics:
|
||||||
|
return const AdminStatisticsPage();
|
||||||
|
case _PageType.history:
|
||||||
|
return const AdminHistoryPage();
|
||||||
|
case _PageType.communication:
|
||||||
|
return const AdminCommunicationPage();
|
||||||
|
case _PageType.map:
|
||||||
|
return const AdminMapPage();
|
||||||
|
case _PageType.amicale:
|
||||||
|
return AdminAmicalePage(
|
||||||
|
userRepository: userRepository,
|
||||||
|
amicaleRepository: amicaleRepository,
|
||||||
|
membreRepository: membreRepository,
|
||||||
|
);
|
||||||
|
case _PageType.operations:
|
||||||
|
return const Scaffold(body: Center(child: Text('Page Opérations')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Construire la liste des destinations de navigation en fonction du rôle
|
// Construire la liste des destinations de navigation en fonction du rôle
|
||||||
List<NavigationDestination> _buildNavigationDestinations() {
|
List<NavigationDestination> _buildNavigationDestinations() {
|
||||||
final destinations = <NavigationDestination>[];
|
final destinations = <NavigationDestination>[];
|
||||||
@@ -145,13 +168,15 @@ class _AdminDashboardPageState extends State<AdminDashboardPage>
|
|||||||
final currentUser = userRepository.getCurrentUser();
|
final currentUser = userRepository.getCurrentUser();
|
||||||
|
|
||||||
// Ajouter les pages de base
|
// Ajouter les pages de base
|
||||||
pages.addAll(_baseNavigationItems.map((item) => item.page));
|
for (final item in _baseNavigationItems) {
|
||||||
|
pages.add(_buildPage(item.pageType));
|
||||||
|
}
|
||||||
|
|
||||||
// Ajouter les pages admin si l'utilisateur a le rôle requis
|
// Ajouter les pages admin si l'utilisateur a le rôle requis
|
||||||
if (currentUser?.role == 2) {
|
if (currentUser?.role == 2) {
|
||||||
for (final item in _adminNavigationItems) {
|
for (final item in _adminNavigationItems) {
|
||||||
if (item.requiredRole == null || item.requiredRole == 2) {
|
if (item.requiredRole == null || item.requiredRole == 2) {
|
||||||
pages.add(item.page);
|
pages.add(_buildPage(item.pageType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,11 +196,9 @@ class _AdminDashboardPageState extends State<AdminDashboardPage>
|
|||||||
debugPrint('userRepository est correctement initialisé');
|
debugPrint('userRepository est correctement initialisé');
|
||||||
final currentUser = userRepository.getCurrentUser();
|
final currentUser = userRepository.getCurrentUser();
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
debugPrint(
|
debugPrint('ERREUR: Aucun utilisateur connecté dans AdminDashboardPage');
|
||||||
'ERREUR: Aucun utilisateur connecté dans AdminDashboardPage');
|
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Utilisateur connecté: ${currentUser.username} (${currentUser.id})');
|
||||||
'Utilisateur connecté: ${currentUser.username} (${currentUser.id})');
|
|
||||||
}
|
}
|
||||||
userRepository.addListener(_handleUserRepositoryChanges);
|
userRepository.addListener(_handleUserRepositoryChanges);
|
||||||
|
|
||||||
@@ -276,7 +299,7 @@ class _AdminDashboardPageState extends State<AdminDashboardPage>
|
|||||||
),
|
),
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: DotsPainter(),
|
painter: DotsPainter(),
|
||||||
child: Container(width: double.infinity, height: double.infinity),
|
child: const SizedBox(width: double.infinity, height: double.infinity),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Contenu de la page
|
// Contenu de la page
|
||||||
@@ -299,19 +322,30 @@ class _AdminDashboardPageState extends State<AdminDashboardPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enum pour les types de pages
|
||||||
|
enum _PageType {
|
||||||
|
dashboardHome,
|
||||||
|
statistics,
|
||||||
|
history,
|
||||||
|
communication,
|
||||||
|
map,
|
||||||
|
amicale,
|
||||||
|
operations,
|
||||||
|
}
|
||||||
|
|
||||||
// Classe pour représenter une destination de navigation avec sa page associée
|
// Classe pour représenter une destination de navigation avec sa page associée
|
||||||
class _NavigationItem {
|
class _NavigationItem {
|
||||||
final String label;
|
final String label;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final IconData selectedIcon;
|
final IconData selectedIcon;
|
||||||
final Widget page;
|
final _PageType pageType;
|
||||||
final int? requiredRole; // null si accessible à tous les rôles
|
final int? requiredRole; // null si accessible à tous les rôles
|
||||||
|
|
||||||
const _NavigationItem({
|
const _NavigationItem({
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.selectedIcon,
|
required this.selectedIcon,
|
||||||
required this.page,
|
required this.pageType,
|
||||||
this.requiredRole,
|
this.requiredRole,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,361 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'dart:math' as math;
|
|
||||||
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
|
|
||||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
|
||||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
|
||||||
import 'package:geosector_app/presentation/widgets/amicale_table_widget.dart';
|
|
||||||
import 'package:geosector_app/presentation/widgets/membre_table_widget.dart';
|
|
||||||
|
|
||||||
/// Class pour dessiner les petits points blancs sur le fond
|
|
||||||
class DotsPainter extends CustomPainter {
|
|
||||||
@override
|
|
||||||
void paint(Canvas canvas, Size size) {
|
|
||||||
final paint = Paint()
|
|
||||||
..color = Colors.white.withOpacity(0.5)
|
|
||||||
..style = PaintingStyle.fill;
|
|
||||||
|
|
||||||
final random = math.Random(42); // Seed fixe pour consistance
|
|
||||||
final numberOfDots = (size.width * size.height) ~/ 1500;
|
|
||||||
|
|
||||||
for (int i = 0; i < numberOfDots; i++) {
|
|
||||||
final x = random.nextDouble() * size.width;
|
|
||||||
final y = random.nextDouble() * size.height;
|
|
||||||
final radius = 1.0 + random.nextDouble() * 2.0;
|
|
||||||
canvas.drawCircle(Offset(x, y), radius, paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Page d'administration de l'amicale et des membres
|
|
||||||
/// Cette page est intégrée dans le tableau de bord administrateur
|
|
||||||
class AdminEntitePage extends StatefulWidget {
|
|
||||||
const AdminEntitePage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<AdminEntitePage> createState() => _AdminEntitePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AdminEntitePageState extends State<AdminEntitePage> {
|
|
||||||
bool _isLoading = true;
|
|
||||||
AmicaleModel? _amicale;
|
|
||||||
List<MembreModel> _membres = [];
|
|
||||||
String? _errorMessage;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadData();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadData() async {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = true;
|
|
||||||
_errorMessage = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Récupérer l'utilisateur connecté en utilisant l'instance globale
|
|
||||||
final currentUser = userRepository.getCurrentUser();
|
|
||||||
|
|
||||||
if (currentUser == null) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = 'Utilisateur non connecté';
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérifier si fkEntite est null
|
|
||||||
if (currentUser.fkEntite == null) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = 'Utilisateur non associé à une amicale';
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer l'amicale de l'utilisateur en utilisant l'instance globale
|
|
||||||
final amicale = amicaleRepository.getAmicaleById(currentUser.fkEntite!);
|
|
||||||
|
|
||||||
if (amicale == null) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = 'Amicale non trouvée';
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer tous les membres
|
|
||||||
// Note: Dans un cas réel, nous devrions filtrer les membres par amicale,
|
|
||||||
// mais le modèle MembreModel n'a pas de champ fkEntite pour le moment
|
|
||||||
final membres = membreRepository.getAllMembres();
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_amicale = amicale;
|
|
||||||
_membres = membres;
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = 'Erreur lors du chargement des données: $e';
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleEditAmicale(AmicaleModel amicale) {
|
|
||||||
// Afficher une boîte de dialogue de confirmation
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Modifier l\'amicale'),
|
|
||||||
content: Text('Voulez-vous modifier l\'amicale ${amicale.name} ?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: const Text('Annuler'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
// Naviguer vers la page de modification
|
|
||||||
// Navigator.of(context).push(
|
|
||||||
// MaterialPageRoute(
|
|
||||||
// builder: (context) => EditAmicalePage(amicale: amicale),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
},
|
|
||||||
child: const Text('Modifier'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleEditMembre(MembreModel membre) {
|
|
||||||
// Afficher une boîte de dialogue de confirmation
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('Modifier le membre'),
|
|
||||||
content: Text(
|
|
||||||
'Voulez-vous modifier le membre ${membre.firstName} ${membre.name} ?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
child: const Text('Annuler'),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
// Naviguer vers la page de modification
|
|
||||||
// Navigator.of(context).push(
|
|
||||||
// MaterialPageRoute(
|
|
||||||
// builder: (context) => EditMembrePage(membre: membre),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
},
|
|
||||||
child: const Text('Modifier'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
// Fond dégradé avec petits points blancs
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: [Colors.white, Colors.red.shade300],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: CustomPaint(
|
|
||||||
painter: DotsPainter(),
|
|
||||||
child: Container(width: double.infinity, height: double.infinity),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Contenu principal
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Titre de la page
|
|
||||||
Text(
|
|
||||||
'Mon amicale et ses membres',
|
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Message d'erreur si présent
|
|
||||||
if (_errorMessage != null)
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
margin: const EdgeInsets.only(bottom: 16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(color: Colors.red.withOpacity(0.3)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.error_outline, color: Colors.red),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
_errorMessage!,
|
|
||||||
style: const TextStyle(color: Colors.red),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Contenu principal
|
|
||||||
if (_isLoading)
|
|
||||||
const Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else if (_amicale == null)
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.business_outlined,
|
|
||||||
size: 64,
|
|
||||||
color: theme.colorScheme.primary.withOpacity(0.7),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
'Aucune amicale associée',
|
|
||||||
style: theme.textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'Vous n\'êtes pas associé à une amicale.',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: theme.textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
// Section Amicale
|
|
||||||
Text(
|
|
||||||
'Informations de l\'amicale',
|
|
||||||
style: theme.textTheme.titleLarge?.copyWith(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Tableau Amicale
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 4,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: AmicaleTableWidget(
|
|
||||||
amicales: [_amicale!],
|
|
||||||
// Pas de bouton de suppression pour sa propre amicale
|
|
||||||
onDelete: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 32),
|
|
||||||
|
|
||||||
// Section Membres
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Membres de l\'amicale',
|
|
||||||
style: theme.textTheme.titleLarge?.copyWith(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ElevatedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
// Naviguer vers la page d'ajout de membre
|
|
||||||
// Navigator.of(context).push(
|
|
||||||
// MaterialPageRoute(
|
|
||||||
// builder: (context) => AddMembrePage(amicaleId: _amicale!.id),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: const Text('Ajouter un membre'),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: theme.colorScheme.primary,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Tableau Membres
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 4,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: MembreTableWidget(
|
|
||||||
membres: _membres,
|
|
||||||
onEdit: _handleEditMembre,
|
|
||||||
// Pas de bouton de suppression pour les membres de sa propre amicale
|
|
||||||
// sauf si l'utilisateur a un rôle élevé
|
|
||||||
onDelete: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -78,9 +78,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
// Fallback sur la version du AppInfoService si elle existe
|
// Fallback sur la version du AppInfoService si elle existe
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_appVersion = AppInfoService.fullVersion
|
_appVersion = AppInfoService.fullVersion.split(' ').last; // Extraire juste le numéro
|
||||||
.split(' ')
|
|
||||||
.last; // Extraire juste le numéro
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +101,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
// Vérification du type de connexion
|
// Vérification du type de connexion
|
||||||
if (widget.loginType == null) {
|
if (widget.loginType == null) {
|
||||||
// Si aucun type n'est spécifié, naviguer vers la splash page
|
// Si aucun type n'est spécifié, naviguer vers la splash page
|
||||||
print(
|
print('LoginPage: Aucun type de connexion spécifié, navigation vers splash page');
|
||||||
'LoginPage: Aucun type de connexion spécifié, navigation vers splash page');
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
GoRouter.of(context).go('/');
|
GoRouter.of(context).go('/');
|
||||||
});
|
});
|
||||||
@@ -157,13 +154,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
'''
|
'''
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (result != null &&
|
if (result != null && result is String && result.toLowerCase() == 'user') {
|
||||||
result is String &&
|
|
||||||
result.toLowerCase() == 'user') {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_loginType = 'user';
|
_loginType = 'user';
|
||||||
print(
|
print('LoginPage: Type détecté depuis sessionStorage: $_loginType');
|
||||||
'LoginPage: Type détecté depuis sessionStorage: $_loginType');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -217,7 +211,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
if (lastUser.role is String) {
|
if (lastUser.role is String) {
|
||||||
roleValue = int.tryParse(lastUser.role as String) ?? 0;
|
roleValue = int.tryParse(lastUser.role as String) ?? 0;
|
||||||
} else {
|
} else {
|
||||||
roleValue = lastUser.role as int;
|
roleValue = lastUser.role;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier si le rôle correspond au type de login
|
// Vérifier si le rôle correspond au type de login
|
||||||
@@ -227,8 +221,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
debugPrint('Rôle utilisateur (1) correspond au type de login (user)');
|
debugPrint('Rôle utilisateur (1) correspond au type de login (user)');
|
||||||
} else if (_loginType == 'admin' && roleValue > 1) {
|
} else if (_loginType == 'admin' && roleValue > 1) {
|
||||||
roleMatches = true;
|
roleMatches = true;
|
||||||
debugPrint(
|
debugPrint('Rôle administrateur ($roleValue) correspond au type de login (admin)');
|
||||||
'Rôle administrateur (${roleValue}) correspond au type de login (admin)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pré-remplir le champ username seulement si le rôle correspond
|
// Pré-remplir le champ username seulement si le rôle correspond
|
||||||
@@ -242,12 +235,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
} else if (lastUser.email.isNotEmpty) {
|
} else if (lastUser.email.isNotEmpty) {
|
||||||
_usernameController.text = lastUser.email;
|
_usernameController.text = lastUser.email;
|
||||||
_usernameFocusNode.unfocus();
|
_usernameFocusNode.unfocus();
|
||||||
debugPrint(
|
debugPrint('Champ username pré-rempli avec email: ${lastUser.email}');
|
||||||
'Champ username pré-rempli avec email: ${lastUser.email}');
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Le rôle ($roleValue) ne correspond pas au type de login ($_loginType), champ username non pré-rempli');
|
||||||
'Le rôle (${roleValue}) ne correspond pas au type de login ($_loginType), champ username non pré-rempli');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -327,14 +318,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: _loginType == 'user'
|
colors: _loginType == 'user' ? [Colors.white, Colors.green.shade300] : [Colors.white, Colors.red.shade300],
|
||||||
? [Colors.white, Colors.green.shade300]
|
|
||||||
: [Colors.white, Colors.red.shade300],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: DotsPainter(),
|
painter: DotsPainter(),
|
||||||
child: Container(width: double.infinity, height: double.infinity),
|
child: const SizedBox(width: double.infinity, height: double.infinity),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
@@ -345,11 +334,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: const BoxConstraints(maxWidth: 500),
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
shadowColor: _loginType == 'user'
|
shadowColor: _loginType == 'user' ? Colors.green.withOpacity(0.5) : Colors.red.withOpacity(0.5),
|
||||||
? Colors.green.withOpacity(0.5)
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)),
|
||||||
: Colors.red.withOpacity(0.5),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16.0)),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -378,9 +364,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.error.withOpacity(0.1),
|
color: theme.colorScheme.error.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(color: theme.colorScheme.error.withOpacity(0.3)),
|
||||||
color:
|
|
||||||
theme.colorScheme.error.withOpacity(0.3)),
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -391,8 +375,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
_locationErrorMessage ??
|
_locationErrorMessage ?? 'L\'accès à la localisation est nécessaire pour utiliser cette application.',
|
||||||
'L\'accès à la localisation est nécessaire pour utiliser cette application.',
|
|
||||||
style: theme.textTheme.bodyLarge,
|
style: theme.textTheme.bodyLarge,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -416,14 +399,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildInstructionStep(theme, 1,
|
_buildInstructionStep(theme, 1, 'Ouvrez les paramètres de votre appareil'),
|
||||||
'Ouvrez les paramètres de votre appareil'),
|
_buildInstructionStep(theme, 2, 'Accédez aux paramètres de confidentialité ou de localisation'),
|
||||||
_buildInstructionStep(theme, 2,
|
_buildInstructionStep(theme, 3, 'Recherchez GEOSECTOR dans la liste des applications'),
|
||||||
'Accédez aux paramètres de confidentialité ou de localisation'),
|
_buildInstructionStep(theme, 4, 'Activez l\'accès à la localisation pour cette application'),
|
||||||
_buildInstructionStep(theme, 3,
|
|
||||||
'Recherchez GEOSECTOR dans la liste des applications'),
|
|
||||||
_buildInstructionStep(theme, 4,
|
|
||||||
'Activez l\'accès à la localisation pour cette application'),
|
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
// Boutons d'action
|
// Boutons d'action
|
||||||
@@ -469,8 +448,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Construit une étape d'instruction pour activer la localisation
|
/// Construit une étape d'instruction pour activer la localisation
|
||||||
Widget _buildInstructionStep(
|
Widget _buildInstructionStep(ThemeData theme, int stepNumber, String instruction) {
|
||||||
ThemeData theme, int stepNumber, String instruction) {
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -530,14 +508,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
end: Alignment.bottomCenter,
|
||||||
colors: _loginType == 'user'
|
colors: _loginType == 'user' ? [Colors.white, Colors.green.shade300] : [Colors.white, Colors.red.shade300],
|
||||||
? [Colors.white, Colors.green.shade300]
|
|
||||||
: [Colors.white, Colors.red.shade300],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: DotsPainter(),
|
painter: DotsPainter(),
|
||||||
child: Container(width: double.infinity, height: double.infinity),
|
child: const SizedBox(width: double.infinity, height: double.infinity),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
@@ -548,11 +524,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: const BoxConstraints(maxWidth: 500),
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
shadowColor: _loginType == 'user'
|
shadowColor: _loginType == 'user' ? Colors.green.withOpacity(0.5) : Colors.red.withOpacity(0.5),
|
||||||
? Colors.green.withOpacity(0.5)
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0)),
|
||||||
: Colors.red.withOpacity(0.5),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(16.0)),
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -566,39 +539,26 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Text(
|
Text(
|
||||||
_loginType == 'user'
|
_loginType == 'user' ? 'Connexion Utilisateur' : 'Connexion Administrateur',
|
||||||
? 'Connexion Utilisateur'
|
|
||||||
: 'Connexion Administrateur',
|
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: _loginType == 'user'
|
color: _loginType == 'user' ? Colors.green : Colors.red,
|
||||||
? Colors.green
|
|
||||||
: Colors.red,
|
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
|
||||||
AppInfoService.fullVersion,
|
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: theme.colorScheme.primary.withOpacity(0.7),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
// Ajouter un texte de débogage uniquement en mode développement
|
// Ajouter un texte de débogage uniquement en mode développement
|
||||||
if (kDebugMode)
|
if (kDebugMode)
|
||||||
Text(
|
Text(
|
||||||
'Type de connexion: $_loginType',
|
'Type de connexion: $_loginType',
|
||||||
style:
|
style: const TextStyle(fontSize: 10, color: Colors.grey),
|
||||||
TextStyle(fontSize: 10, color: Colors.grey),
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Bienvenue sur GEOSECTOR',
|
'Bienvenue sur GEOSECTOR',
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
color: theme.colorScheme.onBackground
|
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||||
.withOpacity(0.7),
|
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -616,23 +576,17 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
color: theme.colorScheme.error.withOpacity(0.1),
|
color: theme.colorScheme.error.withOpacity(0.1),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color:
|
color: theme.colorScheme.error.withOpacity(0.3),
|
||||||
theme.colorScheme.error.withOpacity(0.3),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.signal_wifi_off,
|
Icon(Icons.signal_wifi_off, color: theme.colorScheme.error, size: 32),
|
||||||
color: theme.colorScheme.error, size: 32),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text('Connexion Internet requise',
|
Text('Connexion Internet requise',
|
||||||
style: theme.textTheme.titleMedium
|
style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold, color: theme.colorScheme.error)),
|
||||||
?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: theme.colorScheme.error)),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
const Text(
|
const Text('Veuillez vous connecter à Internet (WiFi ou données mobiles) pour pouvoir vous connecter.'),
|
||||||
'Veuillez vous connecter à Internet (WiFi ou données mobiles) pour pouvoir vous connecter.'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -669,9 +623,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
obscureText: _obscurePassword,
|
obscureText: _obscurePassword,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_obscurePassword
|
_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
|
||||||
? Icons.visibility_outlined
|
|
||||||
: Icons.visibility_off_outlined,
|
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -686,21 +638,17 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onFieldSubmitted: (_) async {
|
onFieldSubmitted: (_) async {
|
||||||
if (!userRepository.isLoading &&
|
if (!userRepository.isLoading && _formKey.currentState!.validate()) {
|
||||||
_formKey.currentState!.validate()) {
|
|
||||||
// Vérifier que le type de connexion est spécifié
|
// Vérifier que le type de connexion est spécifié
|
||||||
if (_loginType.isEmpty) {
|
if (_loginType.isEmpty) {
|
||||||
print(
|
print('Login: Type non spécifié, redirection vers la page de démarrage');
|
||||||
'Login: Type non spécifié, redirection vers la page de démarrage');
|
|
||||||
context.go('/');
|
context.go('/');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print(
|
print('Login: Tentative avec type: $_loginType');
|
||||||
'Login: Tentative avec type: $_loginType');
|
|
||||||
|
|
||||||
final success =
|
final success = await userRepository.login(
|
||||||
await userRepository.login(
|
|
||||||
_usernameController.text.trim(),
|
_usernameController.text.trim(),
|
||||||
_passwordController.text,
|
_passwordController.text,
|
||||||
type: _loginType,
|
type: _loginType,
|
||||||
@@ -708,16 +656,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
|
|
||||||
if (success && mounted) {
|
if (success && mounted) {
|
||||||
// Récupérer directement le rôle de l'utilisateur
|
// Récupérer directement le rôle de l'utilisateur
|
||||||
final user =
|
final user = userRepository.getCurrentUser();
|
||||||
userRepository.getCurrentUser();
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
debugPrint(
|
debugPrint('ERREUR: Utilisateur non trouvé après connexion réussie');
|
||||||
'ERREUR: Utilisateur non trouvé après connexion réussie');
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text('Erreur de connexion. Veuillez réessayer.'),
|
||||||
'Erreur de connexion. Veuillez réessayer.'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -727,32 +671,25 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
// Convertir le rôle en int si nécessaire
|
// Convertir le rôle en int si nécessaire
|
||||||
int roleValue;
|
int roleValue;
|
||||||
if (user.role is String) {
|
if (user.role is String) {
|
||||||
roleValue = int.tryParse(
|
roleValue = int.tryParse(user.role as String) ?? 1;
|
||||||
user.role as String) ??
|
|
||||||
1;
|
|
||||||
} else {
|
} else {
|
||||||
roleValue = user.role as int;
|
roleValue = user.role;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint(
|
debugPrint('Role de l\'utilisateur: $roleValue');
|
||||||
'Role de l\'utilisateur: $roleValue');
|
|
||||||
|
|
||||||
// Redirection simple basée sur le rôle
|
// Redirection simple basée sur le rôle
|
||||||
if (roleValue > 1) {
|
if (roleValue > 1) {
|
||||||
debugPrint(
|
debugPrint('Redirection vers /admin (rôle > 1)');
|
||||||
'Redirection vers /admin (rôle > 1)');
|
|
||||||
context.go('/admin');
|
context.go('/admin');
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Redirection vers /user (rôle = 1)');
|
||||||
'Redirection vers /user (rôle = 1)');
|
|
||||||
context.go('/user');
|
context.go('/user');
|
||||||
}
|
}
|
||||||
} else if (mounted) {
|
} else if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text('Échec de la connexion. Vérifiez vos identifiants.'),
|
||||||
'Échec de la connexion. Vérifiez vos identifiants.'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -781,23 +718,19 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
|
|
||||||
// Bouton de connexion
|
// Bouton de connexion
|
||||||
CustomButton(
|
CustomButton(
|
||||||
onPressed: (userRepository.isLoading ||
|
onPressed: (userRepository.isLoading || !_isConnected)
|
||||||
!_isConnected)
|
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
if (_formKey.currentState!
|
if (_formKey.currentState!.validate()) {
|
||||||
.validate()) {
|
|
||||||
// Vérifier à nouveau les permissions de géolocalisation avant de se connecter (sauf en version web)
|
// Vérifier à nouveau les permissions de géolocalisation avant de se connecter (sauf en version web)
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
await _checkLocationPermission();
|
await _checkLocationPermission();
|
||||||
|
|
||||||
// Si l'utilisateur n'a toujours pas accordé les permissions, ne pas continuer
|
// Si l'utilisateur n'a toujours pas accordé les permissions, ne pas continuer
|
||||||
if (!_hasLocationPermission) {
|
if (!_hasLocationPermission) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text('L\'accès à la localisation est nécessaire pour utiliser cette application.'),
|
||||||
'L\'accès à la localisation est nécessaire pour utiliser cette application.'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -806,36 +739,23 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier la connexion Internet
|
// Vérifier la connexion Internet
|
||||||
await connectivityService
|
await connectivityService.checkConnectivity();
|
||||||
.checkConnectivity();
|
|
||||||
|
|
||||||
if (!connectivityService
|
if (!connectivityService.isConnected) {
|
||||||
.isConnected) {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(
|
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text(
|
content: const Text('Aucune connexion Internet. La connexion n\'est pas possible hors ligne.'),
|
||||||
'Aucune connexion Internet. La connexion n\'est pas possible hors ligne.'),
|
backgroundColor: theme.colorScheme.error,
|
||||||
backgroundColor:
|
duration: const Duration(seconds: 3),
|
||||||
theme.colorScheme.error,
|
|
||||||
duration: const Duration(
|
|
||||||
seconds: 3),
|
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: 'Réessayer',
|
label: 'Réessayer',
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await connectivityService
|
await connectivityService.checkConnectivity();
|
||||||
.checkConnectivity();
|
if (connectivityService.isConnected && mounted) {
|
||||||
if (connectivityService
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.isConnected &&
|
|
||||||
mounted) {
|
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context)
|
|
||||||
.showSnackBar(
|
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text('Connexion Internet ${connectivityService.connectionType} détectée.'),
|
||||||
'Connexion Internet ${connectivityService.connectionType} détectée.'),
|
backgroundColor: Colors.green,
|
||||||
backgroundColor:
|
|
||||||
Colors.green,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -848,18 +768,15 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
|
|
||||||
// Vérifier que le type de connexion est spécifié
|
// Vérifier que le type de connexion est spécifié
|
||||||
if (_loginType.isEmpty) {
|
if (_loginType.isEmpty) {
|
||||||
print(
|
print('Login: Type non spécifié, redirection vers la page de démarrage');
|
||||||
'Login: Type non spécifié, redirection vers la page de démarrage');
|
|
||||||
context.go('/');
|
context.go('/');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print(
|
print('Login: Tentative avec type: $_loginType');
|
||||||
'Login: Tentative avec type: $_loginType');
|
|
||||||
|
|
||||||
// Utiliser directement userRepository avec l'overlay de chargement
|
// Utiliser directement userRepository avec l'overlay de chargement
|
||||||
final success = await userRepository
|
final success = await userRepository.loginWithUI(
|
||||||
.loginWithUI(
|
|
||||||
context,
|
context,
|
||||||
_usernameController.text.trim(),
|
_usernameController.text.trim(),
|
||||||
_passwordController.text,
|
_passwordController.text,
|
||||||
@@ -867,20 +784,15 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (success && mounted) {
|
if (success && mounted) {
|
||||||
debugPrint(
|
debugPrint('Connexion réussie, tentative de redirection...');
|
||||||
'Connexion réussie, tentative de redirection...');
|
|
||||||
|
|
||||||
// Récupérer directement le rôle de l'utilisateur
|
// Récupérer directement le rôle de l'utilisateur
|
||||||
final user = userRepository
|
final user = userRepository.getCurrentUser();
|
||||||
.getCurrentUser();
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
debugPrint(
|
debugPrint('ERREUR: Utilisateur non trouvé après connexion réussie');
|
||||||
'ERREUR: Utilisateur non trouvé après connexion réussie');
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text('Erreur de connexion. Veuillez réessayer.'),
|
||||||
'Erreur de connexion. Veuillez réessayer.'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -890,41 +802,32 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
// Convertir le rôle en int si nécessaire
|
// Convertir le rôle en int si nécessaire
|
||||||
int roleValue;
|
int roleValue;
|
||||||
if (user.role is String) {
|
if (user.role is String) {
|
||||||
roleValue = int.tryParse(
|
roleValue = int.tryParse(user.role as String) ?? 1;
|
||||||
user.role as String) ??
|
|
||||||
1;
|
|
||||||
} else {
|
} else {
|
||||||
roleValue = user.role as int;
|
roleValue = user.role;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugPrint(
|
debugPrint('Role de l\'utilisateur: $roleValue');
|
||||||
'Role de l\'utilisateur: $roleValue');
|
|
||||||
|
|
||||||
// Redirection simple basée sur le rôle
|
// Redirection simple basée sur le rôle
|
||||||
if (roleValue > 1) {
|
if (roleValue > 1) {
|
||||||
debugPrint(
|
debugPrint('Redirection vers /admin (rôle > 1)');
|
||||||
'Redirection vers /admin (rôle > 1)');
|
|
||||||
context.go('/admin');
|
context.go('/admin');
|
||||||
} else {
|
} else {
|
||||||
debugPrint(
|
debugPrint('Redirection vers /user (rôle = 1)');
|
||||||
'Redirection vers /user (rôle = 1)');
|
|
||||||
context.go('/user');
|
context.go('/user');
|
||||||
}
|
}
|
||||||
} else if (mounted) {
|
} else if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text('Échec de la connexion. Vérifiez vos identifiants.'),
|
||||||
'Échec de la connexion. Vérifiez vos identifiants.'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: _isConnected
|
text: _isConnected ? 'Se connecter' : 'Connexion Internet requise',
|
||||||
? 'Se connecter'
|
|
||||||
: 'Connexion Internet requise',
|
|
||||||
isLoading: userRepository.isLoading,
|
isLoading: userRepository.isLoading,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
@@ -1047,8 +950,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Veuillez entrer votre email';
|
return 'Veuillez entrer votre email';
|
||||||
}
|
}
|
||||||
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
|
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
|
||||||
.hasMatch(value)) {
|
|
||||||
return 'Veuillez entrer un email valide';
|
return 'Veuillez entrer un email valide';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -1105,10 +1007,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
// Si la réponse est 404, c'est peut-être un problème de route
|
// Si la réponse est 404, c'est peut-être un problème de route
|
||||||
if (response.statusCode == 404) {
|
if (response.statusCode == 404) {
|
||||||
// Essayer avec une URL alternative
|
// Essayer avec une URL alternative
|
||||||
final alternativeUrl =
|
final alternativeUrl = '$baseUrl/api/index.php/lostpassword';
|
||||||
'$baseUrl/api/index.php/lostpassword';
|
print('Tentative avec URL alternative: $alternativeUrl');
|
||||||
print(
|
|
||||||
'Tentative avec URL alternative: $alternativeUrl');
|
|
||||||
|
|
||||||
final alternativeResponse = await http.post(
|
final alternativeResponse = await http.post(
|
||||||
Uri.parse(alternativeUrl),
|
Uri.parse(alternativeUrl),
|
||||||
@@ -1118,10 +1018,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
print(
|
print('Réponse alternative reçue: ${alternativeResponse.statusCode}');
|
||||||
'Réponse alternative reçue: ${alternativeResponse.statusCode}');
|
print('Corps de la réponse alternative: ${alternativeResponse.body}');
|
||||||
print(
|
|
||||||
'Corps de la réponse alternative: ${alternativeResponse.body}');
|
|
||||||
|
|
||||||
// Si la réponse alternative est un succès, utiliser cette réponse
|
// Si la réponse alternative est un succès, utiliser cette réponse
|
||||||
if (alternativeResponse.statusCode == 200) {
|
if (alternativeResponse.statusCode == 200) {
|
||||||
@@ -1129,14 +1027,12 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(
|
print('Erreur lors de l\'envoi de la requête: $e');
|
||||||
'Erreur lors de l\'envoi de la requête: $e');
|
|
||||||
throw Exception('Erreur de connexion: $e');
|
throw Exception('Erreur de connexion: $e');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traiter la réponse
|
// Traiter la réponse
|
||||||
if (response != null &&
|
if (response.statusCode == 200) {
|
||||||
response.statusCode == 200) {
|
|
||||||
// Modifier le contenu de la boîte de dialogue pour afficher le message de succès
|
// Modifier le contenu de la boîte de dialogue pour afficher le message de succès
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@@ -1148,7 +1044,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
// Fermer automatiquement la boîte de dialogue après 2 secondes
|
// Fermer automatiquement la boîte de dialogue après 2 secondes
|
||||||
Future.delayed(Duration(seconds: 2), () {
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
if (Navigator.of(context).canPop()) {
|
if (Navigator.of(context).canPop()) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
@@ -1180,16 +1076,13 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
|
|
||||||
// Afficher un message d'erreur
|
// Afficher un message d'erreur
|
||||||
final responseData = json.decode(response.body);
|
final responseData = json.decode(response.body);
|
||||||
throw Exception(responseData['message'] ??
|
throw Exception(responseData['message'] ?? 'Erreur lors de la récupération du mot de passe');
|
||||||
'Erreur lors de la récupération du mot de passe');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Afficher un message d'erreur
|
// Afficher un message d'erreur
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(e
|
content: Text(e.toString().contains('Exception:')
|
||||||
.toString()
|
|
||||||
.contains('Exception:')
|
|
||||||
? e.toString().split('Exception: ')[1]
|
? e.toString().split('Exception: ')[1]
|
||||||
: 'Erreur lors de la récupération du mot de passe'),
|
: 'Erreur lors de la récupération du mot de passe'),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
@@ -1204,21 +1097,20 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: isLoading
|
|
||||||
? SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
valueColor:
|
|
||||||
AlwaysStoppedAnimation<Color>(Colors.white),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text('Recevoir un nouveau mot de passe'),
|
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blue,
|
backgroundColor: Colors.blue,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
|
child: isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Text('Recevoir un nouveau mot de passe'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -73,10 +73,8 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
final String _hiddenToken = DateTime.now().millisecondsSinceEpoch.toString();
|
final String _hiddenToken = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
|
||||||
// Valeurs pour le captcha simple
|
// Valeurs pour le captcha simple
|
||||||
final int _captchaNum1 =
|
final int _captchaNum1 = 2 + (DateTime.now().second % 5); // Nombre entre 2 et 6
|
||||||
2 + (DateTime.now().second % 5); // Nombre entre 2 et 6
|
final int _captchaNum2 = 3 + (DateTime.now().minute % 4); // Nombre entre 3 et 6
|
||||||
final int _captchaNum2 =
|
|
||||||
3 + (DateTime.now().minute % 4); // Nombre entre 3 et 6
|
|
||||||
|
|
||||||
// État de la connexion Internet et de la plateforme
|
// État de la connexion Internet et de la plateforme
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
@@ -102,9 +100,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
// Fallback sur la version du AppInfoService si elle existe
|
// Fallback sur la version du AppInfoService si elle existe
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_appVersion = AppInfoService.fullVersion
|
_appVersion = AppInfoService.fullVersion.split(' ').last; // Extraire juste le numéro
|
||||||
.split(' ')
|
|
||||||
.last; // Extraire juste le numéro
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,8 +164,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Utiliser l'API interne de geosector pour récupérer les villes par code postal
|
// Utiliser l'API interne de geosector pour récupérer les villes par code postal
|
||||||
final baseUrl = Uri
|
final baseUrl = Uri.base.origin; // Récupère l'URL de base (ex: https://app.geosector.fr)
|
||||||
.base.origin; // Récupère l'URL de base (ex: https://app.geosector.fr)
|
|
||||||
final apiUrl = '$baseUrl/api/villes?code_postal=$postalCode';
|
final apiUrl = '$baseUrl/api/villes?code_postal=$postalCode';
|
||||||
|
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
@@ -251,7 +246,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
),
|
),
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: DotsPainter(),
|
painter: DotsPainter(),
|
||||||
child: Container(width: double.infinity, height: double.infinity),
|
child: const SizedBox(width: double.infinity, height: double.infinity),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SafeArea(
|
SafeArea(
|
||||||
@@ -282,8 +277,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
Text(
|
Text(
|
||||||
'Enregistrez votre amicale sur GeoSector',
|
'Enregistrez votre amicale sur GeoSector',
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
color:
|
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||||
theme.colorScheme.onBackground.withOpacity(0.7),
|
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -295,8 +289,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
if (mounted && _isConnected != isConnected) {
|
if (mounted && _isConnected != isConnected) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isConnected = isConnected;
|
_isConnected = isConnected;
|
||||||
_connectionType =
|
_connectionType = connectivityService.connectionType;
|
||||||
connectivityService.connectionType;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -343,8 +336,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
if (_isConnected && mounted) {
|
if (_isConnected && mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text('Connexion Internet $_connectionType détectée.'),
|
||||||
'Connexion Internet $_connectionType détectée.'),
|
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -396,8 +388,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Veuillez entrer votre email';
|
return 'Veuillez entrer votre email';
|
||||||
}
|
}
|
||||||
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
|
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
|
||||||
.hasMatch(value)) {
|
|
||||||
return 'Veuillez entrer un email valide';
|
return 'Veuillez entrer un email valide';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -424,8 +415,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
CustomTextField(
|
CustomTextField(
|
||||||
controller: _postalCodeController,
|
controller: _postalCodeController,
|
||||||
label: 'Code postal de l\'amicale',
|
label: 'Code postal de l\'amicale',
|
||||||
hintText:
|
hintText: 'Entrez le code postal de votre amicale',
|
||||||
'Entrez le code postal de votre amicale',
|
|
||||||
prefixIcon: Icons.location_on_outlined,
|
prefixIcon: Icons.location_on_outlined,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
@@ -453,13 +443,12 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Commune de l\'amicale',
|
'Commune de l\'amicale',
|
||||||
style:
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
theme.textTheme.titleSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: theme.colorScheme.onBackground,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
const Text(
|
||||||
' •',
|
' •',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
@@ -484,8 +473,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
),
|
),
|
||||||
child: _isLoadingCities
|
child: _isLoadingCities
|
||||||
? const Padding(
|
? const Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
vertical: 16),
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
@@ -497,20 +485,16 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
Icons.location_city_outlined,
|
Icons.location_city_outlined,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
hintText: _postalCodeController
|
hintText: _postalCodeController.text.length < 3
|
||||||
.text.length <
|
|
||||||
3
|
|
||||||
? 'Entrez d\'abord au moins 3 chiffres du code postal'
|
? 'Entrez d\'abord au moins 3 chiffres du code postal'
|
||||||
: _cities.isEmpty
|
: _cities.isEmpty
|
||||||
? 'Aucune commune trouvée pour ce code postal'
|
? 'Aucune commune trouvée pour ce code postal'
|
||||||
: 'Sélectionnez une commune',
|
: 'Sélectionnez une commune',
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(12),
|
||||||
BorderRadius.circular(12),
|
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
),
|
),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
vertical: 16,
|
vertical: 16,
|
||||||
),
|
),
|
||||||
@@ -528,18 +512,13 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
// Mettre à jour le code postal avec celui de la ville sélectionnée
|
// Mettre à jour le code postal avec celui de la ville sélectionnée
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
// Désactiver temporairement le listener pour éviter une boucle infinie
|
// Désactiver temporairement le listener pour éviter une boucle infinie
|
||||||
_postalCodeController
|
_postalCodeController.removeListener(_onPostalCodeChanged);
|
||||||
.removeListener(
|
|
||||||
_onPostalCodeChanged);
|
|
||||||
|
|
||||||
// Mettre à jour le code postal
|
// Mettre à jour le code postal
|
||||||
_postalCodeController.text =
|
_postalCodeController.text = newValue.postalCode;
|
||||||
newValue.postalCode;
|
|
||||||
|
|
||||||
// Réactiver le listener
|
// Réactiver le listener
|
||||||
_postalCodeController
|
_postalCodeController.addListener(_onPostalCodeChanged);
|
||||||
.addListener(
|
|
||||||
_onPostalCodeChanged);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -574,8 +553,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
CustomTextField(
|
CustomTextField(
|
||||||
controller: _captchaController,
|
controller: _captchaController,
|
||||||
label:
|
label: 'Combien font $_captchaNum1 + $_captchaNum2 ?',
|
||||||
'Combien font $_captchaNum1 + $_captchaNum2 ?',
|
|
||||||
hintText: 'Entrez le résultat',
|
hintText: 'Entrez le résultat',
|
||||||
prefixIcon: Icons.security,
|
prefixIcon: Icons.security,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
@@ -612,43 +590,30 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
|
|
||||||
// Bouton d'inscription
|
// Bouton d'inscription
|
||||||
CustomButton(
|
CustomButton(
|
||||||
onPressed: (_isLoading ||
|
onPressed: (_isLoading || (_isMobile && !_isConnected))
|
||||||
(_isMobile && !_isConnected))
|
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
// Vérifier la connexion Internet avant de soumettre
|
// Vérifier la connexion Internet avant de soumettre
|
||||||
// Utiliser l'instance globale de connectivityService définie dans app.dart
|
// Utiliser l'instance globale de connectivityService définie dans app.dart
|
||||||
await connectivityService
|
await connectivityService.checkConnectivity();
|
||||||
.checkConnectivity();
|
|
||||||
|
|
||||||
if (!connectivityService.isConnected) {
|
if (!connectivityService.isConnected) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.showSnackBar(
|
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: const Text(
|
content: const Text('Aucune connexion Internet. L\'inscription nécessite une connexion active.'),
|
||||||
'Aucune connexion Internet. L\'inscription nécessite une connexion active.'),
|
backgroundColor: theme.colorScheme.error,
|
||||||
backgroundColor:
|
duration: const Duration(seconds: 3),
|
||||||
theme.colorScheme.error,
|
|
||||||
duration:
|
|
||||||
const Duration(seconds: 3),
|
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: 'Réessayer',
|
label: 'Réessayer',
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await connectivityService
|
await connectivityService.checkConnectivity();
|
||||||
.checkConnectivity();
|
if (connectivityService.isConnected && mounted) {
|
||||||
if (connectivityService
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.isConnected &&
|
|
||||||
mounted) {
|
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context)
|
|
||||||
.showSnackBar(
|
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text('Connexion Internet ${connectivityService.connectionType} détectée.'),
|
||||||
'Connexion Internet ${connectivityService.connectionType} détectée.'),
|
backgroundColor: Colors.green,
|
||||||
backgroundColor:
|
|
||||||
Colors.green,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -660,15 +625,11 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Vérifier que le captcha est correct
|
// Vérifier que le captcha est correct
|
||||||
final int? captchaAnswer = int.tryParse(
|
final int? captchaAnswer = int.tryParse(_captchaController.text);
|
||||||
_captchaController.text);
|
if (captchaAnswer != _captchaNum1 + _captchaNum2) {
|
||||||
if (captchaAnswer !=
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
_captchaNum1 + _captchaNum2) {
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(
|
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text('La vérification de sécurité a échoué. Veuillez réessayer.'),
|
||||||
'La vérification de sécurité a échoué. Veuillez réessayer.'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -679,16 +640,11 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
final Map<String, dynamic> formData = {
|
final Map<String, dynamic> formData = {
|
||||||
'email': _emailController.text.trim(),
|
'email': _emailController.text.trim(),
|
||||||
'name': _nameController.text.trim(),
|
'name': _nameController.text.trim(),
|
||||||
'amicale_name': _amicaleNameController
|
'amicale_name': _amicaleNameController.text.trim(),
|
||||||
.text
|
'postal_code': _postalCodeController.text,
|
||||||
.trim(),
|
'city_name': _selectedCity?.name ?? '',
|
||||||
'postal_code':
|
|
||||||
_postalCodeController.text,
|
|
||||||
'city_name':
|
|
||||||
_selectedCity?.name ?? '',
|
|
||||||
'captcha_answer': captchaAnswer,
|
'captcha_answer': captchaAnswer,
|
||||||
'captcha_expected':
|
'captcha_expected': _captchaNum1 + _captchaNum2,
|
||||||
_captchaNum1 + _captchaNum2,
|
|
||||||
'token': _hiddenToken,
|
'token': _hiddenToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -700,14 +656,12 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
try {
|
try {
|
||||||
// Envoyer les données à l'API
|
// Envoyer les données à l'API
|
||||||
final baseUrl = Uri.base.origin;
|
final baseUrl = Uri.base.origin;
|
||||||
final apiUrl =
|
final apiUrl = '$baseUrl/api/register';
|
||||||
'$baseUrl/api/register';
|
|
||||||
|
|
||||||
final response = await http.post(
|
final response = await http.post(
|
||||||
Uri.parse(apiUrl),
|
Uri.parse(apiUrl),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type':
|
'Content-Type': 'application/json',
|
||||||
'application/json',
|
|
||||||
},
|
},
|
||||||
body: json.encode(formData),
|
body: json.encode(formData),
|
||||||
);
|
);
|
||||||
@@ -718,34 +672,23 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Traiter la réponse
|
// Traiter la réponse
|
||||||
if (response.statusCode == 200 ||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
response.statusCode == 201) {
|
final responseData = json.decode(response.body);
|
||||||
final responseData =
|
|
||||||
json.decode(response.body);
|
|
||||||
|
|
||||||
// Vérifier si la réponse indique un succès
|
// Vérifier si la réponse indique un succès
|
||||||
final bool isSuccess =
|
final bool isSuccess = responseData['success'] == true || responseData['status'] == 'success';
|
||||||
responseData['success'] ==
|
|
||||||
true ||
|
|
||||||
responseData['status'] ==
|
|
||||||
'success';
|
|
||||||
|
|
||||||
// Récupérer le message de la réponse
|
// Récupérer le message de la réponse
|
||||||
final String message = responseData[
|
final String message = responseData['message'] ??
|
||||||
'message'] ??
|
(isSuccess ? 'Inscription réussie !' : 'Échec de l\'inscription. Veuillez réessayer.');
|
||||||
(isSuccess
|
|
||||||
? 'Inscription réussie !'
|
|
||||||
: 'Échec de l\'inscription. Veuillez réessayer.');
|
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Afficher une boîte de dialogue de succès
|
// Afficher une boîte de dialogue de succès
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible:
|
barrierDismissible: false, // L'utilisateur doit cliquer sur OK
|
||||||
false, // L'utilisateur doit cliquer sur OK
|
builder: (BuildContext context) {
|
||||||
builder:
|
|
||||||
(BuildContext context) {
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Row(
|
title: const Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -754,84 +697,50 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
),
|
),
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
Text(
|
Text('Inscription réussie'),
|
||||||
'Inscription réussie'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize:
|
mainAxisSize: MainAxisSize.min,
|
||||||
MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment
|
|
||||||
.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Votre demande d\'inscription a été enregistrée avec succès.',
|
'Votre demande d\'inscription a été enregistrée avec succès.',
|
||||||
style: theme
|
style: theme.textTheme.bodyLarge,
|
||||||
.textTheme
|
|
||||||
.bodyLarge,
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Vous allez recevoir un email contenant :',
|
'Vous allez recevoir un email contenant :',
|
||||||
style: theme
|
style: theme.textTheme.bodyMedium,
|
||||||
.textTheme
|
|
||||||
.bodyMedium,
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment
|
|
||||||
.start,
|
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(Icons.arrow_right, size: 20, color: theme.colorScheme.primary),
|
||||||
Icons
|
const SizedBox(width: 4),
|
||||||
.arrow_right,
|
const Expanded(
|
||||||
size: 20,
|
child: Text('Votre identifiant de connexion'),
|
||||||
color: theme
|
|
||||||
.colorScheme
|
|
||||||
.primary),
|
|
||||||
const SizedBox(
|
|
||||||
width: 4),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Votre identifiant de connexion'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment
|
|
||||||
.start,
|
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(Icons.arrow_right, size: 20, color: theme.colorScheme.primary),
|
||||||
Icons
|
const SizedBox(width: 4),
|
||||||
.arrow_right,
|
const Expanded(
|
||||||
size: 20,
|
child: Text('Un lien pour définir votre mot de passe'),
|
||||||
color: theme
|
|
||||||
.colorScheme
|
|
||||||
.primary),
|
|
||||||
SizedBox(
|
|
||||||
width: 4),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'Un lien pour définir votre mot de passe'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Vérifiez votre boîte de réception et vos spams.',
|
'Vérifiez votre boîte de réception et vos spams.',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontStyle:
|
fontStyle: FontStyle.italic,
|
||||||
FontStyle
|
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
||||||
.italic,
|
|
||||||
color: theme
|
|
||||||
.colorScheme
|
|
||||||
.onSurface
|
|
||||||
.withOpacity(
|
|
||||||
0.7),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -839,25 +748,15 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(
|
Navigator.of(context).pop();
|
||||||
context)
|
|
||||||
.pop();
|
|
||||||
// Rediriger vers la page de connexion
|
// Rediriger vers la page de connexion
|
||||||
context
|
context.go('/login');
|
||||||
.go('/login');
|
|
||||||
},
|
},
|
||||||
child: Text('OK'),
|
style: TextButton.styleFrom(
|
||||||
style: TextButton
|
foregroundColor: theme.colorScheme.primary,
|
||||||
.styleFrom(
|
textStyle: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
foregroundColor:
|
|
||||||
theme
|
|
||||||
.colorScheme
|
|
||||||
.primary,
|
|
||||||
textStyle: TextStyle(
|
|
||||||
fontWeight:
|
|
||||||
FontWeight
|
|
||||||
.bold),
|
|
||||||
),
|
),
|
||||||
|
child: const Text('OK'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -870,21 +769,16 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
// Afficher un message d'erreur plus visible
|
// Afficher un message d'erreur plus visible
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder:
|
builder: (BuildContext context) {
|
||||||
(BuildContext context) {
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text(
|
title: const Text('Erreur d\'inscription'),
|
||||||
'Erreur d\'inscription'),
|
|
||||||
content: Text(message),
|
content: Text(message),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(
|
Navigator.of(context).pop();
|
||||||
context)
|
|
||||||
.pop();
|
|
||||||
},
|
},
|
||||||
child:
|
child: const Text('OK'),
|
||||||
const Text('OK'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -892,8 +786,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Afficher également un SnackBar
|
// Afficher également un SnackBar
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.showSnackBar(
|
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(message),
|
content: Text(message),
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
@@ -904,11 +797,9 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
} else {
|
} else {
|
||||||
// Gérer les erreurs HTTP
|
// Gérer les erreurs HTTP
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.showSnackBar(
|
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text('Erreur ${response.statusCode}: ${response.reasonPhrase ?? "Échec de l'inscription"}'),
|
||||||
'Erreur ${response.statusCode}: ${response.reasonPhrase ?? "Échec de l\'inscription"}'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -922,11 +813,9 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
|
|
||||||
// Gérer les exceptions
|
// Gérer les exceptions
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
.showSnackBar(
|
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text('Erreur: ${e.toString()}'),
|
||||||
'Erreur: ${e.toString()}'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -934,9 +823,7 @@ class _RegisterPageState extends State<RegisterPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text: (_isMobile && !_isConnected)
|
text: (_isMobile && !_isConnected) ? 'Connexion Internet requise' : 'Enregistrer mon amicale',
|
||||||
? 'Connexion Internet requise'
|
|
||||||
: 'Enregistrer mon amicale',
|
|
||||||
isLoading: _isLoading,
|
isLoading: _isLoading,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|||||||
@@ -6,20 +6,23 @@ import 'package:geosector_app/core/repositories/user_repository.dart';
|
|||||||
import 'package:geosector_app/core/services/api_service.dart';
|
import 'package:geosector_app/core/services/api_service.dart';
|
||||||
import 'package:geosector_app/presentation/widgets/mapbox_map.dart';
|
import 'package:geosector_app/presentation/widgets/mapbox_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'custom_text_field.dart';
|
import 'custom_text_field.dart';
|
||||||
|
|
||||||
class AmicaleForm extends StatefulWidget {
|
class AmicaleForm extends StatefulWidget {
|
||||||
final AmicaleModel? amicale;
|
final AmicaleModel? amicale;
|
||||||
final Function(AmicaleModel)? onSubmit;
|
final Function(AmicaleModel)? onSubmit;
|
||||||
final bool readOnly;
|
final bool readOnly;
|
||||||
|
final UserRepository userRepository; // Nouveau paramètre
|
||||||
|
final ApiService? apiService; // Nouveau paramètre optionnel
|
||||||
|
|
||||||
const AmicaleForm({
|
const AmicaleForm({
|
||||||
Key? key,
|
super.key,
|
||||||
this.amicale,
|
this.amicale,
|
||||||
this.onSubmit,
|
this.onSubmit,
|
||||||
this.readOnly = false,
|
this.readOnly = false,
|
||||||
}) : super(key: key);
|
required this.userRepository, // Requis
|
||||||
|
this.apiService, // Optionnel
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AmicaleForm> createState() => _AmicaleFormState();
|
State<AmicaleForm> createState() => _AmicaleFormState();
|
||||||
@@ -59,8 +62,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
_nameController = TextEditingController(text: amicale?.name ?? '');
|
_nameController = TextEditingController(text: amicale?.name ?? '');
|
||||||
_adresse1Controller = TextEditingController(text: amicale?.adresse1 ?? '');
|
_adresse1Controller = TextEditingController(text: amicale?.adresse1 ?? '');
|
||||||
_adresse2Controller = TextEditingController(text: amicale?.adresse2 ?? '');
|
_adresse2Controller = TextEditingController(text: amicale?.adresse2 ?? '');
|
||||||
_codePostalController =
|
_codePostalController = TextEditingController(text: amicale?.codePostal ?? '');
|
||||||
TextEditingController(text: amicale?.codePostal ?? '');
|
|
||||||
_villeController = TextEditingController(text: amicale?.ville ?? '');
|
_villeController = TextEditingController(text: amicale?.ville ?? '');
|
||||||
_phoneController = TextEditingController(text: amicale?.phone ?? '');
|
_phoneController = TextEditingController(text: amicale?.phone ?? '');
|
||||||
_mobileController = TextEditingController(text: amicale?.mobile ?? '');
|
_mobileController = TextEditingController(text: amicale?.mobile ?? '');
|
||||||
@@ -125,9 +127,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Ajouter les champs réservés aux administrateurs si l'utilisateur est admin
|
// Ajouter les champs réservés aux administrateurs si l'utilisateur est admin
|
||||||
final userRepository =
|
final userRole = widget.userRepository.getUserRole();
|
||||||
Provider.of<UserRepository>(context, listen: false);
|
|
||||||
final userRole = userRepository.getUserRole();
|
|
||||||
if (userRole > 2) {
|
if (userRole > 2) {
|
||||||
data['gps_lat'] = amicale.gpsLat;
|
data['gps_lat'] = amicale.gpsLat;
|
||||||
data['gps_lng'] = amicale.gpsLng;
|
data['gps_lng'] = amicale.gpsLng;
|
||||||
@@ -136,56 +136,71 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
data['chk_active'] = amicale.chkActive;
|
data['chk_active'] = amicale.chkActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Appeler l'API
|
// Fermer l'indicateur de chargement
|
||||||
try {
|
Navigator.of(context).pop();
|
||||||
// Obtenir l'instance du service API
|
|
||||||
final apiService = Provider.of<ApiService>(context, listen: false);
|
|
||||||
|
|
||||||
// Appeler la méthode post du service API
|
// Appeler l'API si le service est disponible
|
||||||
await apiService.post('/entite/update', data: data);
|
if (widget.apiService != null) {
|
||||||
|
try {
|
||||||
|
await widget.apiService!.post('/entite/update', data: data);
|
||||||
|
|
||||||
// Fermer l'indicateur de chargement
|
// Afficher un message de succès
|
||||||
Navigator.of(context).pop();
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
// Afficher un message de succès
|
const SnackBar(
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
content: Text('Amicale mise à jour avec succès'),
|
||||||
const SnackBar(
|
backgroundColor: Colors.green,
|
||||||
content: Text('Amicale mise à jour avec succès'),
|
),
|
||||||
backgroundColor: Colors.green,
|
);
|
||||||
),
|
}
|
||||||
);
|
} catch (error) {
|
||||||
|
// Afficher un message d'erreur
|
||||||
// Appeler la fonction onSubmit si elle existe
|
if (mounted) {
|
||||||
if (widget.onSubmit != null) {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
widget.onSubmit!(amicale);
|
SnackBar(
|
||||||
|
content: Text('Erreur lors de la mise à jour de l\'amicale: $error'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return; // Sortir de la fonction en cas d'erreur
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Pas d'API service, afficher un message d'information
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Modifications enregistrées localement'),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fermer le formulaire
|
// Appeler la fonction onSubmit si elle existe
|
||||||
Navigator.of(context).pop();
|
if (widget.onSubmit != null) {
|
||||||
} catch (error) {
|
widget.onSubmit!(amicale);
|
||||||
// Fermer l'indicateur de chargement
|
}
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
// Afficher un message d'erreur
|
// Fermer le formulaire
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Fermer l'indicateur de chargement si encore ouvert
|
||||||
|
if (Navigator.of(context).canPop()) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher un message d'erreur
|
||||||
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content:
|
content: Text('Erreur: ${e.toString()}'),
|
||||||
Text('Erreur lors de la mise à jour de l\'amicale: $error'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
// Fermer l'indicateur de chargement
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
// Afficher un message d'erreur
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text('Erreur: ${e.toString()}'),
|
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,13 +210,13 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
if (_phoneController.text.isEmpty && _mobileController.text.isEmpty) {
|
if (_phoneController.text.isEmpty && _mobileController.text.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content:
|
content: Text('Veuillez renseigner au moins un numéro de téléphone'),
|
||||||
Text('Veuillez renseigner au moins un numéro de téléphone'),
|
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final amicale = widget.amicale?.copyWith(
|
final amicale = widget.amicale?.copyWith(
|
||||||
name: _nameController.text,
|
name: _nameController.text,
|
||||||
adresse1: _adresse1Controller.text,
|
adresse1: _adresse1Controller.text,
|
||||||
@@ -246,10 +261,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
// Appeler l'API pour mettre à jour l'amicale
|
// Appeler l'API pour mettre à jour l'amicale
|
||||||
_updateAmicale(amicale);
|
_updateAmicale(amicale);
|
||||||
|
|
||||||
// Appeler la fonction onSubmit si elle existe (pour la compatibilité avec le code existant)
|
// Ne pas appeler widget.onSubmit ici car c'est fait dans _updateAmicale
|
||||||
if (widget.onSubmit != null) {
|
|
||||||
widget.onSubmit!(amicale);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,8 +305,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
// TODO: Implémenter la sélection d'image
|
// TODO: Implémenter la sélection d'image
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text('Fonctionnalité de modification du logo à venir'),
|
||||||
'Fonctionnalité de modification du logo à venir'),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -447,7 +458,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
label,
|
label,
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -481,7 +492,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
"Adresse",
|
"Adresse",
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: theme.colorScheme.onBackground,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -566,7 +577,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
"Région",
|
"Région",
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: theme.colorScheme.onBackground,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -580,7 +591,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
"Contact",
|
"Contact",
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: theme.colorScheme.onBackground,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -657,7 +668,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
"Informations avancées",
|
"Informations avancées",
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: theme.colorScheme.onBackground,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -671,8 +682,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
controller: _gpsLatController,
|
controller: _gpsLatController,
|
||||||
label: "GPS Latitude",
|
label: "GPS Latitude",
|
||||||
keyboardType:
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
readOnly: restrictedFieldsReadOnly,
|
readOnly: restrictedFieldsReadOnly,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -682,8 +692,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
child: CustomTextField(
|
child: CustomTextField(
|
||||||
controller: _gpsLngController,
|
controller: _gpsLngController,
|
||||||
label: "GPS Longitude",
|
label: "GPS Longitude",
|
||||||
keyboardType:
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
readOnly: restrictedFieldsReadOnly,
|
readOnly: restrictedFieldsReadOnly,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -749,7 +758,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
Text(
|
Text(
|
||||||
"Accepte les règlements en CB",
|
"Accepte les règlements en CB",
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
color: theme.colorScheme.onBackground,
|
color: theme.colorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -760,8 +769,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
controller: _stripeIdController,
|
controller: _stripeIdController,
|
||||||
label: "ID Stripe Paiements CB",
|
label: "ID Stripe Paiements CB",
|
||||||
readOnly: restrictedFieldsReadOnly,
|
readOnly: restrictedFieldsReadOnly,
|
||||||
helperText:
|
helperText: "Les règlements par CB sont taxés d'une commission de 1.4%",
|
||||||
"Les règlements par CB sont taxés d'une commission de 1.4%",
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -774,7 +782,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
"Options",
|
"Options",
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.titleMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: theme.colorScheme.onBackground,
|
color: theme.colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -849,8 +857,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: const Color(0xFF20335E),
|
foregroundColor: const Color(0xFF20335E),
|
||||||
side: const BorderSide(color: Color(0xFF20335E)),
|
side: const BorderSide(color: Color(0xFF20335E)),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
horizontal: 24, vertical: 16),
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
),
|
),
|
||||||
@@ -871,8 +878,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFF20335E),
|
backgroundColor: const Color(0xFF20335E),
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||||
horizontal: 24, vertical: 16),
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(50),
|
borderRadius: BorderRadius.circular(50),
|
||||||
),
|
),
|
||||||
@@ -895,70 +901,73 @@ class _AmicaleFormState extends State<AmicaleForm> {
|
|||||||
|
|
||||||
// Vérifier si les informations avancées doivent être affichées
|
// Vérifier si les informations avancées doivent être affichées
|
||||||
bool _shouldShowAdvancedInfo() {
|
bool _shouldShowAdvancedInfo() {
|
||||||
final userRepository = Provider.of<UserRepository>(context, listen: false);
|
final userRole = widget.userRepository.getUserRole();
|
||||||
final userRole = userRepository.getUserRole();
|
|
||||||
final bool canEditRestrictedFields = userRole > 2;
|
final bool canEditRestrictedFields = userRole > 2;
|
||||||
|
|
||||||
return canEditRestrictedFields ||
|
return canEditRestrictedFields || _gpsLatController.text.isNotEmpty || _gpsLngController.text.isNotEmpty || _stripeIdController.text.isNotEmpty;
|
||||||
_gpsLatController.text.isNotEmpty ||
|
|
||||||
_gpsLngController.text.isNotEmpty ||
|
|
||||||
_stripeIdController.text.isNotEmpty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final userRepository = Provider.of<UserRepository>(context, listen: false);
|
final userRole = widget.userRepository.getUserRole();
|
||||||
final userRole = userRepository.getUserRole();
|
|
||||||
|
|
||||||
// Déterminer si l'utilisateur peut modifier les champs restreints
|
// Déterminer si l'utilisateur peut modifier les champs restreints
|
||||||
final bool canEditRestrictedFields = userRole > 2;
|
final bool canEditRestrictedFields = userRole > 2;
|
||||||
|
|
||||||
// Lecture seule pour les champs restreints si l'utilisateur n'a pas les droits
|
// Lecture seule pour les champs restreints si l'utilisateur n'a pas les droits
|
||||||
final bool restrictedFieldsReadOnly =
|
final bool restrictedFieldsReadOnly = widget.readOnly || !canEditRestrictedFields;
|
||||||
widget.readOnly || !canEditRestrictedFields;
|
|
||||||
|
|
||||||
// Calculer la largeur maximale du formulaire pour les écrans larges
|
// Calculer la largeur maximale du formulaire pour les écrans larges
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
final maxFormWidth = screenWidth > 800 ? 800.0 : screenWidth;
|
final maxFormWidth = screenWidth > 800 ? 800.0 : screenWidth;
|
||||||
|
|
||||||
return Scaffold(
|
final formContent = Container(
|
||||||
appBar: AppBar(
|
width: maxFormWidth,
|
||||||
title: Text(
|
padding: const EdgeInsets.all(16),
|
||||||
widget.readOnly ? 'Détails de l\'amicale' : 'Modifier l\'amicale'),
|
child: Form(
|
||||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
key: _formKey,
|
||||||
foregroundColor: theme.appBarTheme.foregroundColor,
|
child: SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
body: Center(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Container(
|
children: [
|
||||||
width: maxFormWidth,
|
// Header avec logo et minimap
|
||||||
padding: const EdgeInsets.all(16),
|
Row(
|
||||||
child: Form(
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
key: _formKey,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
// Header avec logo et minimap
|
// Section Logo
|
||||||
Row(
|
_buildLogoSection(),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
// Section MiniMap
|
||||||
children: [
|
_buildMiniMap(),
|
||||||
// Section Logo
|
|
||||||
_buildLogoSection(),
|
|
||||||
// Section MiniMap
|
|
||||||
_buildMiniMap(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Formulaire principal
|
|
||||||
_buildMainForm(theme, restrictedFieldsReadOnly),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Formulaire principal
|
||||||
|
_buildMainForm(theme, restrictedFieldsReadOnly),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Vérifier si on est dans une Dialog en regardant le type du widget parent
|
||||||
|
final route = ModalRoute.of(context);
|
||||||
|
final isInDialog = route?.settings.name == null;
|
||||||
|
|
||||||
|
// Si on est dans une Dialog, ne pas utiliser Scaffold
|
||||||
|
if (isInDialog) {
|
||||||
|
return Center(child: formContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sinon, utiliser Scaffold pour les pages complètes
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(widget.readOnly ? 'Détails de l\'amicale' : 'Modifier l\'amicale'),
|
||||||
|
backgroundColor: theme.appBarTheme.backgroundColor,
|
||||||
|
foregroundColor: theme.appBarTheme.foregroundColor,
|
||||||
|
),
|
||||||
|
body: Center(child: formContent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,33 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geosector_app/app.dart';
|
|
||||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||||
|
|
||||||
/// Widget pour afficher une ligne du tableau d'amicales
|
/// Widget pour afficher une ligne du tableau d'amicales
|
||||||
/// Affiche les colonnes id, name, codePostal, libRegion et une colonne Actions
|
/// Affiche les colonnes id, name, codePostal, libRegion et une colonne Actions (conditionnelle)
|
||||||
/// La colonne Actions contient un bouton Delete pour les utilisateurs avec rôle > 2
|
/// La colonne Actions contient des boutons Edit et Delete selon les permissions
|
||||||
/// La ligne entière est cliquable pour afficher les détails de l'amicale
|
/// Pour un admin d'amicale (rôle 2), seule la ligne est cliquable sans colonne Actions
|
||||||
class AmicaleRowWidget extends StatelessWidget {
|
class AmicaleRowWidget extends StatelessWidget {
|
||||||
final AmicaleModel amicale;
|
final AmicaleModel amicale;
|
||||||
final Function(AmicaleModel)? onTap;
|
final Function(AmicaleModel)? onTap;
|
||||||
|
final Function(AmicaleModel)? onEdit;
|
||||||
final Function(AmicaleModel)? onDelete;
|
final Function(AmicaleModel)? onDelete;
|
||||||
final bool isHeader;
|
final bool isHeader;
|
||||||
final bool isAlternate;
|
final bool isAlternate;
|
||||||
|
final bool showActionsColumn;
|
||||||
|
|
||||||
const AmicaleRowWidget({
|
const AmicaleRowWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.amicale,
|
required this.amicale,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.onEdit,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
this.isHeader = false,
|
this.isHeader = false,
|
||||||
this.isAlternate = false,
|
this.isAlternate = false,
|
||||||
}) : super(key: key);
|
this.showActionsColumn = true,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final userRole = userRepository.getUserRole();
|
|
||||||
|
|
||||||
// Définir les styles en fonction du type de ligne (en-tête ou données)
|
// Définir les styles en fonction du type de ligne (en-tête ou données)
|
||||||
final textStyle = isHeader
|
final textStyle = isHeader
|
||||||
@@ -36,11 +38,7 @@ class AmicaleRowWidget extends StatelessWidget {
|
|||||||
: theme.textTheme.bodyMedium;
|
: theme.textTheme.bodyMedium;
|
||||||
|
|
||||||
// Couleur de fond en fonction du type de ligne
|
// Couleur de fond en fonction du type de ligne
|
||||||
final backgroundColor = isHeader
|
final backgroundColor = isHeader ? theme.colorScheme.primary.withOpacity(0.1) : (isAlternate ? theme.colorScheme.surface : theme.colorScheme.surface);
|
||||||
? theme.colorScheme.primary.withOpacity(0.1)
|
|
||||||
: (isAlternate
|
|
||||||
? theme.colorScheme.surface
|
|
||||||
: theme.colorScheme.background);
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: isHeader || onTap == null ? null : () => onTap!(amicale),
|
onTap: isHeader || onTap == null ? null : () => onTap!(amicale),
|
||||||
@@ -55,7 +53,7 @@ class AmicaleRowWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// Colonne ID
|
// Colonne ID
|
||||||
@@ -103,7 +101,7 @@ class AmicaleRowWidget extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
isHeader ? 'Ville' : (amicale.ville ?? ''),
|
isHeader ? 'Ville' : amicale.ville,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -123,8 +121,8 @@ class AmicaleRowWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Colonne Actions - seulement si l'utilisateur a le rôle > 2 et onDelete n'est pas null
|
// Colonne Actions (conditionnelle)
|
||||||
if (isHeader || (userRole > 2 && onDelete != null))
|
if (showActionsColumn && (isHeader || onEdit != null || onDelete != null))
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -138,22 +136,40 @@ class AmicaleRowWidget extends StatelessWidget {
|
|||||||
: Row(
|
: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
// Bouton Edit
|
||||||
|
if (onEdit != null)
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.edit,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
tooltip: 'Modifier',
|
||||||
|
onPressed: () => onEdit!(amicale),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 36,
|
||||||
|
minHeight: 36,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
),
|
||||||
// Bouton Delete
|
// Bouton Delete
|
||||||
IconButton(
|
if (onDelete != null)
|
||||||
icon: Icon(
|
IconButton(
|
||||||
Icons.delete,
|
icon: Icon(
|
||||||
color: theme.colorScheme.error,
|
Icons.delete,
|
||||||
size: 20,
|
color: theme.colorScheme.error,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
tooltip: 'Supprimer',
|
||||||
|
onPressed: () => onDelete!(amicale),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 36,
|
||||||
|
minHeight: 36,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
tooltip: 'Supprimer',
|
|
||||||
onPressed: () => onDelete!(amicale),
|
|
||||||
constraints: const BoxConstraints(
|
|
||||||
minWidth: 36,
|
|
||||||
minHeight: 36,
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
visualDensity: VisualDensity.compact,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geosector_app/app.dart';
|
import 'package:geosector_app/core/services/api_service.dart';
|
||||||
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
import 'package:geosector_app/core/data/models/amicale_model.dart';
|
||||||
import 'package:geosector_app/core/repositories/region_repository.dart';
|
|
||||||
import 'package:geosector_app/core/repositories/user_repository.dart';
|
import 'package:geosector_app/core/repositories/user_repository.dart';
|
||||||
|
import 'package:geosector_app/core/repositories/amicale_repository.dart';
|
||||||
import 'package:geosector_app/presentation/widgets/amicale_row_widget.dart';
|
import 'package:geosector_app/presentation/widgets/amicale_row_widget.dart';
|
||||||
import 'package:geosector_app/presentation/widgets/amicale_form.dart';
|
import 'package:geosector_app/presentation/widgets/amicale_form.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
/// Widget de tableau pour afficher une liste d'amicales
|
/// Widget de tableau pour afficher une liste d'amicales
|
||||||
///
|
///
|
||||||
@@ -19,19 +18,83 @@ import 'package:provider/provider.dart';
|
|||||||
/// Lorsqu'on clique sur une ligne, une modale s'affiche avec le formulaire EntiteForm
|
/// Lorsqu'on clique sur une ligne, une modale s'affiche avec le formulaire EntiteForm
|
||||||
class AmicaleTableWidget extends StatelessWidget {
|
class AmicaleTableWidget extends StatelessWidget {
|
||||||
final List<AmicaleModel> amicales;
|
final List<AmicaleModel> amicales;
|
||||||
|
final Function(AmicaleModel)? onEdit;
|
||||||
final Function(AmicaleModel)? onDelete;
|
final Function(AmicaleModel)? onDelete;
|
||||||
|
final AmicaleRepository amicaleRepository;
|
||||||
|
final UserRepository userRepository; // Nouveau paramètre
|
||||||
|
final ApiService? apiService; // Nouveau paramètre optionnel
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final String? emptyMessage;
|
final String? emptyMessage;
|
||||||
final bool readOnly;
|
final bool readOnly;
|
||||||
|
final bool showActionsColumn;
|
||||||
|
|
||||||
const AmicaleTableWidget({
|
const AmicaleTableWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.amicales,
|
required this.amicales,
|
||||||
|
required this.amicaleRepository,
|
||||||
|
required this.userRepository, // Requis
|
||||||
|
this.onEdit,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
|
this.apiService, // Optionnel
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
this.emptyMessage,
|
this.emptyMessage,
|
||||||
this.readOnly = false,
|
this.readOnly = false,
|
||||||
}) : super(key: key);
|
this.showActionsColumn = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ajouter cette nouvelle méthode pour ouvrir directement le formulaire d'édition :
|
||||||
|
void _showAmicaleEditForm(BuildContext context, AmicaleModel amicale) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (dialogContext) => Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(dialogContext).size.width * 0.9,
|
||||||
|
height: MediaQuery.of(dialogContext).size.height * 0.9,
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Header de la dialog
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Modifier l\'amicale',
|
||||||
|
style: Theme.of(dialogContext).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(dialogContext).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
|
||||||
|
// Contenu du formulaire
|
||||||
|
Expanded(
|
||||||
|
child: AmicaleForm(
|
||||||
|
amicale: amicale,
|
||||||
|
readOnly: false,
|
||||||
|
userRepository: userRepository,
|
||||||
|
apiService: apiService,
|
||||||
|
onSubmit: (updatedAmicale) {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
// La mise à jour sera gérée par les ValueListenableBuilder
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -51,7 +114,9 @@ class AmicaleTableWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
isHeader: true,
|
isHeader: true,
|
||||||
onTap: null,
|
onTap: null,
|
||||||
|
onEdit: null,
|
||||||
onDelete: null,
|
onDelete: null,
|
||||||
|
showActionsColumn: showActionsColumn,
|
||||||
),
|
),
|
||||||
|
|
||||||
// Corps du tableau
|
// Corps du tableau
|
||||||
@@ -90,8 +155,7 @@ class AmicaleTableWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
emptyMessage ?? 'Aucune amicale trouvée',
|
emptyMessage ?? 'Aucune amicale trouvée',
|
||||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
color:
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -107,10 +171,18 @@ class AmicaleTableWidget extends StatelessWidget {
|
|||||||
final amicale = amicales[index];
|
final amicale = amicales[index];
|
||||||
return AmicaleRowWidget(
|
return AmicaleRowWidget(
|
||||||
amicale: amicale,
|
amicale: amicale,
|
||||||
isAlternate: index % 2 == 1, // Alterner les couleurs
|
isAlternate: index % 2 == 1,
|
||||||
onTap: (selectedAmicale) =>
|
onTap: (selectedAmicale) {
|
||||||
_showAmicaleDetails(context, selectedAmicale),
|
// Si pas de colonne Actions, ouvrir directement le formulaire d'édition
|
||||||
|
if (!showActionsColumn) {
|
||||||
|
_showAmicaleEditForm(context, selectedAmicale);
|
||||||
|
} else {
|
||||||
|
_showAmicaleDetails(context, selectedAmicale);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEdit: onEdit,
|
||||||
onDelete: onDelete,
|
onDelete: onDelete,
|
||||||
|
showActionsColumn: showActionsColumn,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -118,63 +190,48 @@ class AmicaleTableWidget extends StatelessWidget {
|
|||||||
|
|
||||||
// Afficher une modale avec le formulaire EntiteForm
|
// Afficher une modale avec le formulaire EntiteForm
|
||||||
void _showAmicaleDetails(BuildContext context, AmicaleModel amicale) {
|
void _showAmicaleDetails(BuildContext context, AmicaleModel amicale) {
|
||||||
// Utiliser l'instance globale de userRepository définie dans app.dart
|
|
||||||
final userRepo = userRepository;
|
|
||||||
// Créer une instance de RegionRepository
|
|
||||||
final regionRepo = RegionRepository();
|
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => MultiProvider(
|
builder: (dialogContext) => Dialog(
|
||||||
providers: [
|
shape: RoundedRectangleBorder(
|
||||||
// Fournir les repositories nécessaires au formulaire
|
borderRadius: BorderRadius.circular(16),
|
||||||
Provider<UserRepository>.value(value: userRepo),
|
),
|
||||||
Provider<RegionRepository>.value(value: regionRepo),
|
child: Container(
|
||||||
],
|
width: MediaQuery.of(dialogContext).size.width * 0.6,
|
||||||
child: Dialog(
|
padding: const EdgeInsets.all(24),
|
||||||
shape: RoundedRectangleBorder(
|
child: SingleChildScrollView(
|
||||||
borderRadius: BorderRadius.circular(16),
|
child: Column(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Container(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
width: MediaQuery.of(dialogContext).size.width * 0.6,
|
children: [
|
||||||
padding: const EdgeInsets.all(24),
|
Row(
|
||||||
child: SingleChildScrollView(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
'Détails de l\'amicale',
|
||||||
children: [
|
style: Theme.of(dialogContext).textTheme.headlineSmall?.copyWith(
|
||||||
Row(
|
fontWeight: FontWeight.bold,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
color: Theme.of(dialogContext).colorScheme.primary,
|
||||||
children: [
|
),
|
||||||
Text(
|
),
|
||||||
'Détails de l\'amicale',
|
IconButton(
|
||||||
style: Theme.of(dialogContext)
|
icon: const Icon(Icons.close),
|
||||||
.textTheme
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||||
.headlineSmall
|
),
|
||||||
?.copyWith(
|
],
|
||||||
fontWeight: FontWeight.bold,
|
),
|
||||||
color:
|
const SizedBox(height: 16),
|
||||||
Theme.of(dialogContext).colorScheme.primary,
|
// Formulaire AmicaleForm en mode lecture seule
|
||||||
),
|
AmicaleForm(
|
||||||
),
|
amicale: amicale,
|
||||||
IconButton(
|
readOnly: true,
|
||||||
icon: const Icon(Icons.close),
|
userRepository: userRepository,
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
apiService: apiService,
|
||||||
),
|
onSubmit: (updatedAmicale) {
|
||||||
],
|
Navigator.of(dialogContext).pop();
|
||||||
),
|
},
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
// Formulaire EntiteForm en mode lecture seule
|
],
|
||||||
AmicaleForm(
|
|
||||||
amicale: amicale,
|
|
||||||
readOnly: readOnly,
|
|
||||||
onSubmit: (updatedAmicale) {
|
|
||||||
Navigator.of(dialogContext).pop();
|
|
||||||
// Ici, vous pourriez ajouter une logique pour mettre à jour l'amicale
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
final VoidCallback? onLogoutPressed;
|
final VoidCallback? onLogoutPressed;
|
||||||
|
|
||||||
const DashboardAppBar({
|
const DashboardAppBar({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.pageTitle,
|
this.pageTitle,
|
||||||
this.showNewPassageButton = true,
|
this.showNewPassageButton = true,
|
||||||
this.onNewPassagePressed,
|
this.onNewPassagePressed,
|
||||||
this.isAdmin = false,
|
this.isAdmin = false,
|
||||||
this.onLogoutPressed,
|
this.onLogoutPressed,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -82,10 +82,12 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
actions.add(const SizedBox(width: 8));
|
||||||
|
|
||||||
// Ajouter la version de l'application
|
// Ajouter la version de l'application
|
||||||
actions.add(
|
actions.add(
|
||||||
Text(
|
Text(
|
||||||
AppInfoService.fullVersion,
|
"v${AppInfoService.version}",
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.white70,
|
color: Colors.white70,
|
||||||
@@ -93,11 +95,12 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
actions.add(const SizedBox(width: 8));
|
||||||
|
|
||||||
actions.add(
|
actions.add(
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.add_location_alt, color: Colors.white),
|
icon: const Icon(Icons.add_location_alt, color: Colors.white),
|
||||||
label: const Text('Nouveau passage',
|
label: const Text('Nouveau passage', style: TextStyle(color: Colors.white)),
|
||||||
style: TextStyle(color: Colors.white)),
|
|
||||||
onPressed: onNewPassagePressed,
|
onPressed: onNewPassagePressed,
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor: theme.colorScheme.secondary,
|
backgroundColor: theme.colorScheme.secondary,
|
||||||
@@ -106,6 +109,8 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
actions.add(const SizedBox(width: 8));
|
||||||
|
|
||||||
// Ajouter le bouton "Mon compte"
|
// Ajouter le bouton "Mon compte"
|
||||||
actions.add(
|
actions.add(
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -128,6 +133,8 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
actions.add(const SizedBox(width: 8));
|
||||||
|
|
||||||
// Ajouter le bouton de déconnexion
|
// Ajouter le bouton de déconnexion
|
||||||
actions.add(
|
actions.add(
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -139,8 +146,7 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
title: const Text('Déconnexion'),
|
title: const Text('Déconnexion'),
|
||||||
content:
|
content: const Text('Voulez-vous vraiment vous déconnecter ?'),
|
||||||
const Text('Voulez-vous vraiment vous déconnecter ?'),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(),
|
onPressed: () => Navigator.of(dialogContext).pop(),
|
||||||
@@ -157,8 +163,7 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
// Vérification supplémentaire et navigation forcée si nécessaire
|
// Vérification supplémentaire et navigation forcée si nécessaire
|
||||||
if (success && context.mounted) {
|
if (success && context.mounted) {
|
||||||
// Attendre un court instant pour que les changements d'état se propagent
|
// Attendre un court instant pour que les changements d'état se propagent
|
||||||
await Future.delayed(
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
const Duration(milliseconds: 100));
|
|
||||||
|
|
||||||
// Navigation forcée vers la page d'accueil
|
// Navigation forcée vers la page d'accueil
|
||||||
context.go('/');
|
context.go('/');
|
||||||
|
|||||||
@@ -3,110 +3,207 @@ import 'package:geosector_app/core/data/models/membre_model.dart';
|
|||||||
|
|
||||||
class MembreRowWidget extends StatelessWidget {
|
class MembreRowWidget extends StatelessWidget {
|
||||||
final MembreModel membre;
|
final MembreModel membre;
|
||||||
final Function()? onEdit;
|
final Function(MembreModel)? onEdit;
|
||||||
final Function()? onDelete;
|
final Function(MembreModel)? onDelete;
|
||||||
|
final bool isAlternate;
|
||||||
|
|
||||||
const MembreRowWidget({
|
const MembreRowWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.membre,
|
required this.membre,
|
||||||
this.onEdit,
|
this.onEdit,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
}) : super(key: key);
|
this.isAlternate = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return Container(
|
// Couleur de fond alternée
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
final backgroundColor = isAlternate ? theme.colorScheme.surface : theme.colorScheme.surface;
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.white,
|
return InkWell(
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
onTap: () => _showMembreDetails(context),
|
||||||
boxShadow: [
|
child: Container(
|
||||||
BoxShadow(
|
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
|
||||||
color: Colors.black.withOpacity(0.05),
|
decoration: BoxDecoration(
|
||||||
blurRadius: 4,
|
color: backgroundColor,
|
||||||
offset: const Offset(0, 2),
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// ID
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
membre.id.toString(),
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Prénom (firstName)
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
membre.firstName,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Nom (name)
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
membre.name,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Email
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(
|
||||||
|
membre.email,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Rôle (fkRole)
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
_getRoleName(membre.fkRole),
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Statut (actif/inactif)
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.3) : Colors.red.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
membre.chkActive == 1 ? 'Actif' : 'Inactif',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: membre.chkActive == 1 ? Colors.green[700] : Colors.red[700],
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
if (onEdit != null || onDelete != null)
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
// Bouton Edit
|
||||||
|
if (onEdit != null)
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.edit,
|
||||||
|
size: 20,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
onPressed: () => onEdit!(membre),
|
||||||
|
tooltip: 'Modifier',
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 36,
|
||||||
|
minHeight: 36,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Espacement entre les boutons
|
||||||
|
if (onEdit != null && onDelete != null) const SizedBox(width: 8),
|
||||||
|
|
||||||
|
// Bouton Delete
|
||||||
|
if (onDelete != null)
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.delete,
|
||||||
|
size: 20,
|
||||||
|
color: theme.colorScheme.error,
|
||||||
|
),
|
||||||
|
onPressed: () => onDelete!(membre),
|
||||||
|
tooltip: 'Supprimer',
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 36,
|
||||||
|
minHeight: 36,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher les détails du membre dans une boîte de dialogue
|
||||||
|
void _showMembreDetails(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text('${membre.firstName} ${membre.name}'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildDetailRow('ID', membre.id.toString()),
|
||||||
|
_buildDetailRow('Email', membre.email),
|
||||||
|
_buildDetailRow('Username', membre.username),
|
||||||
|
_buildDetailRow('Rôle', _getRoleName(membre.fkRole)),
|
||||||
|
_buildDetailRow('Titre', membre.fkTitre.toString()),
|
||||||
|
_buildDetailRow('Secteur', membre.sectName ?? 'Non défini'),
|
||||||
|
_buildDetailRow('Statut', membre.chkActive == 1 ? 'Actif' : 'Inactif'),
|
||||||
|
if (membre.dateNaissance != null)
|
||||||
|
_buildDetailRow('Date de naissance', '${membre.dateNaissance!.day}/${membre.dateNaissance!.month}/${membre.dateNaissance!.year}'),
|
||||||
|
if (membre.dateEmbauche != null)
|
||||||
|
_buildDetailRow('Date d\'embauche', '${membre.dateEmbauche!.day}/${membre.dateEmbauche!.month}/${membre.dateEmbauche!.year}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Fermer'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetailRow(String label, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// ID
|
SizedBox(
|
||||||
Expanded(
|
width: 120,
|
||||||
flex: 1,
|
|
||||||
child: Text(
|
child: Text(
|
||||||
membre.id.toString(),
|
'$label:',
|
||||||
style: theme.textTheme.bodyMedium,
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Prénom (firstName)
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
child: Text(value),
|
||||||
child: Text(
|
|
||||||
membre.firstName,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Nom (name)
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Text(
|
|
||||||
membre.name,
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Secteur (sectName)
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Text(
|
|
||||||
membre.sectName ?? '',
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Rôle (fkRole)
|
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Text(
|
|
||||||
_getRoleName(membre.fkRole),
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
// Bouton Edit
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.edit, size: 20),
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
onPressed: onEdit,
|
|
||||||
tooltip: 'Modifier',
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Bouton Delete
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.delete, size: 20),
|
|
||||||
color: theme.colorScheme.error,
|
|
||||||
onPressed: onDelete,
|
|
||||||
tooltip: 'Supprimer',
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,24 +1,31 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geosector_app/core/data/models/membre_model.dart';
|
import 'package:geosector_app/core/data/models/membre_model.dart';
|
||||||
|
import 'package:geosector_app/core/repositories/membre_repository.dart';
|
||||||
import 'package:geosector_app/presentation/widgets/membre_row_widget.dart';
|
import 'package:geosector_app/presentation/widgets/membre_row_widget.dart';
|
||||||
|
|
||||||
class MembreTableWidget extends StatelessWidget {
|
class MembreTableWidget extends StatelessWidget {
|
||||||
final List<MembreModel> membres;
|
final List<MembreModel> membres;
|
||||||
final Function(MembreModel)? onEdit;
|
final Function(MembreModel)? onEdit;
|
||||||
final Function(MembreModel)? onDelete;
|
final Function(MembreModel)? onDelete;
|
||||||
|
final MembreRepository membreRepository;
|
||||||
final bool showHeader;
|
final bool showHeader;
|
||||||
final double? height;
|
final double? height;
|
||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
|
final bool isLoading;
|
||||||
|
final String? emptyMessage;
|
||||||
|
|
||||||
const MembreTableWidget({
|
const MembreTableWidget({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.membres,
|
required this.membres,
|
||||||
|
required this.membreRepository,
|
||||||
this.onEdit,
|
this.onEdit,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
this.showHeader = true,
|
this.showHeader = true,
|
||||||
this.height,
|
this.height,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
this.isLoading = false,
|
||||||
|
this.emptyMessage,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -44,8 +51,7 @@ class MembreTableWidget extends StatelessWidget {
|
|||||||
// En-tête du tableau
|
// En-tête du tableau
|
||||||
if (showHeader)
|
if (showHeader)
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
|
||||||
const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// ID
|
// ID
|
||||||
@@ -55,6 +61,7 @@ class MembreTableWidget extends StatelessWidget {
|
|||||||
'ID',
|
'ID',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -66,6 +73,7 @@ class MembreTableWidget extends StatelessWidget {
|
|||||||
'Prénom',
|
'Prénom',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -77,17 +85,19 @@ class MembreTableWidget extends StatelessWidget {
|
|||||||
'Nom',
|
'Nom',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Secteur (sectName)
|
// Email
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 3,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Secteur',
|
'Email',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -99,51 +109,83 @@ class MembreTableWidget extends StatelessWidget {
|
|||||||
'Rôle',
|
'Rôle',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Actions
|
// Statut
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 1,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Actions',
|
'Statut',
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.end,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Actions (si onEdit ou onDelete sont fournis)
|
||||||
|
if (onEdit != null || onDelete != null)
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
'Actions',
|
||||||
|
style: theme.textTheme.titleSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Liste des membres
|
// Corps du tableau
|
||||||
Expanded(
|
Expanded(
|
||||||
child: membres.isEmpty
|
child: _buildTableContent(context),
|
||||||
? Center(
|
|
||||||
child: Text(
|
|
||||||
'Aucun membre disponible',
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.separated(
|
|
||||||
itemCount: membres.length,
|
|
||||||
separatorBuilder: (context, index) =>
|
|
||||||
const SizedBox(height: 8.0),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final membre = membres[index];
|
|
||||||
return MembreRowWidget(
|
|
||||||
membre: membre,
|
|
||||||
onEdit: onEdit != null ? () => onEdit!(membre) : null,
|
|
||||||
onDelete:
|
|
||||||
onDelete != null ? () => onDelete!(membre) : null,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildTableContent(BuildContext context) {
|
||||||
|
// Afficher un indicateur de chargement si isLoading est true
|
||||||
|
if (isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher un message si la liste est vide
|
||||||
|
if (membres.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
emptyMessage ?? 'Aucun membre trouvé',
|
||||||
|
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher la liste des membres
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: membres.length,
|
||||||
|
separatorBuilder: (context, index) => Divider(
|
||||||
|
color: Theme.of(context).dividerColor.withOpacity(0.3),
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final membre = membres[index];
|
||||||
|
return MembreRowWidget(
|
||||||
|
membre: membre,
|
||||||
|
onEdit: onEdit,
|
||||||
|
onDelete: onDelete,
|
||||||
|
isAlternate: index % 2 == 1,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: geosector_app
|
name: geosector_app
|
||||||
description: 'GEOSECTOR - Gestion de distribution des calendriers par secteurs géographiques pour les amicales de pompiers'
|
description: 'GEOSECTOR - Gestion de distribution des calendriers par secteurs géographiques pour les amicales de pompiers'
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.3.2
|
version: 0.3.3
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
# 🎯 Prompt : Widget Formulaire Passage Modal Cross-Platform
|
|
||||||
|
|
||||||
## Contexte
|
|
||||||
Développement d'un widget modal de formulaire de passage pour GEOSECTOR, compatible Svelte (web) et Flutter (mobile).
|
|
||||||
|
|
||||||
## Prompt à utiliser avec Cline/Claude
|
|
||||||
|
|
||||||
```
|
|
||||||
Je développe un widget modal de formulaire de passage pour GEOSECTOR qui doit fonctionner de manière identique sur :
|
|
||||||
- Web avec Svelte/SvelteKit
|
|
||||||
- Mobile avec Flutter
|
|
||||||
|
|
||||||
### Spécifications du widget
|
|
||||||
|
|
||||||
#### Interface utilisateur
|
|
||||||
- Modal centré avec overlay semi-transparent
|
|
||||||
- Formulaire avec 4 champs principaux :
|
|
||||||
1. **Date** : sélecteur de date natif
|
|
||||||
2. **Heure** : sélecteur d'heure (format 24h)
|
|
||||||
3. **Commentaire** : textarea avec compteur (max 500 caractères)
|
|
||||||
4. **Statut** : dropdown avec options ["En attente", "Validé", "Rejeté", "En cours"]
|
|
||||||
|
|
||||||
#### Fonctionnalités avancées
|
|
||||||
- Validation en temps réel avec messages d'erreur contextuels
|
|
||||||
- Sauvegarde automatique toutes les 30 secondes (draft local)
|
|
||||||
- Animations d'ouverture/fermeture fluides
|
|
||||||
- Gestion des états : loading, success, error
|
|
||||||
- Boutons d'action : Annuler, Sauvegarder (draft), Valider (final)
|
|
||||||
|
|
||||||
#### Contraintes techniques
|
|
||||||
- Design system cohérent entre Svelte et Flutter
|
|
||||||
- Accessibilité : labels, focus management, navigation clavier
|
|
||||||
- Responsive : adaptation automatique mobile/desktop
|
|
||||||
- Performance : lazy loading du composant
|
|
||||||
- Validation côté client + préparation validation serveur
|
|
||||||
|
|
||||||
### Structure souhaitée
|
|
||||||
|
|
||||||
#### Svelte (lib/components/PassageFormModal.svelte)
|
|
||||||
```
|
|
||||||
<script>
|
|
||||||
// Props et logique de validation
|
|
||||||
// Store pour gestion état
|
|
||||||
// Auto-save logic
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="modal-overlay">
|
|
||||||
<div class="modal-content">
|
|
||||||
<form>
|
|
||||||
<!-- Champs formulaire -->
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Flutter (lib/widgets/passage_form_modal.dart)
|
|
||||||
```
|
|
||||||
class PassageFormModal extends StatefulWidget {
|
|
||||||
// Widget avec même logique qu'en Svelte
|
|
||||||
// Form validation
|
|
||||||
// Auto-save functionality
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Peux-tu m'accompagner dans le développement de ce widget en respectant ces spécifications et en optimisant pour la réutilisabilité ?
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bonnes pratiques à appliquer
|
|
||||||
- Utiliser des validateurs côté client robustes
|
|
||||||
- Implémenter un système de debouncing pour l'auto-save
|
|
||||||
- Gérer proprement les animations et transitions
|
|
||||||
- Prévoir la gestion d'erreur réseau
|
|
||||||
- Tester l'accessibilité sur les deux plateformes
|
|
||||||
|
|
||||||
## Tests suggérés
|
|
||||||
- Tests unitaires de validation
|
|
||||||
- Tests d'intégration du formulaire
|
|
||||||
- Tests d'accessibilité
|
|
||||||
- Tests de performance (auto-save)
|
|
||||||
3
web/.vscode/extensions.json
vendored
3
web/.vscode/extensions.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["svelte.svelte-vscode"]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user