Release v3.1.4 - Mode terrain et génération PDF #10

Closed
Pierre wants to merge 15 commits from release/v3.1.4 into main
270 changed files with 373141 additions and 9939 deletions
Showing only changes of commit f3f1a9c5e8 - Show all commits

598
api/docs/geo_app.sql Normal file
View File

@@ -0,0 +1,598 @@
-- -------------------------------------------------------------
-- TablePlus 6.4.8(608)
--
-- https://tableplus.com/
--
-- Database: geo_app
-- Generation Time: 2025-06-09 18:03:43.5140
-- -------------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE TABLE `chat_anonymous_users` (
`id` varchar(50) NOT NULL,
`device_id` varchar(100) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`converted_to_user_id` int(10) unsigned DEFAULT NULL,
`metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),
PRIMARY KEY (`id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_converted_user` (`converted_to_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_attachments` (
`id` varchar(50) NOT NULL,
`fk_message` varchar(50) NOT NULL,
`file_name` varchar(255) NOT NULL,
`file_path` varchar(500) NOT NULL,
`file_type` varchar(100) NOT NULL,
`file_size` int(10) unsigned NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_message` (`fk_message`),
CONSTRAINT `fk_chat_attachments_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_audience_targets` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_room` varchar(50) NOT NULL,
`target_type` enum('role','entity','all','combined') NOT NULL DEFAULT 'all',
`target_id` varchar(50) DEFAULT NULL,
`role_filter` varchar(20) DEFAULT NULL,
`entity_filter` varchar(50) DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_type` (`target_type`),
CONSTRAINT `fk_chat_audience_targets_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_broadcast_lists` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_room` varchar(50) NOT NULL,
`name` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`fk_user_creator` int(10) unsigned NOT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_user_creator` (`fk_user_creator`),
CONSTRAINT `fk_chat_broadcast_lists_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_messages` (
`id` varchar(50) NOT NULL,
`fk_room` varchar(50) NOT NULL,
`fk_user` int(10) unsigned DEFAULT NULL,
`sender_type` enum('user','anonymous','system') NOT NULL DEFAULT 'user',
`content` text DEFAULT NULL,
`content_type` enum('text','image','file') NOT NULL DEFAULT 'text',
`date_sent` timestamp NOT NULL DEFAULT current_timestamp(),
`date_delivered` timestamp NULL DEFAULT NULL,
`date_read` timestamp NULL DEFAULT NULL,
`statut` enum('envoye','livre','lu','error') NOT NULL DEFAULT 'envoye',
`is_announcement` tinyint(1) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_user` (`fk_user`),
KEY `idx_date` (`date_sent`),
KEY `idx_status` (`statut`),
KEY `idx_messages_unread` (`fk_room`,`statut`),
CONSTRAINT `fk_chat_messages_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_notifications` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`fk_user` int(10) unsigned NOT NULL,
`fk_message` varchar(50) DEFAULT NULL,
`fk_room` varchar(50) DEFAULT NULL,
`type` varchar(50) NOT NULL,
`contenu` text DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
`date_lecture` timestamp NULL DEFAULT NULL,
`statut` enum('non_lue','lue') NOT NULL DEFAULT 'non_lue',
PRIMARY KEY (`id`),
KEY `idx_user` (`fk_user`),
KEY `idx_message` (`fk_message`),
KEY `idx_room` (`fk_room`),
KEY `idx_statut` (`statut`),
KEY `idx_notifications_unread` (`fk_user`,`statut`),
CONSTRAINT `fk_chat_notifications_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_chat_notifications_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_offline_queue` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`operation_type` varchar(50) NOT NULL,
`operation_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`operation_data`)),
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`processed_at` timestamp NULL DEFAULT NULL,
`status` enum('pending','processing','completed','failed') NOT NULL DEFAULT 'pending',
`error_message` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_participants` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_room` varchar(50) NOT NULL,
`id_user` int(10) unsigned DEFAULT NULL,
`anonymous_id` varchar(50) DEFAULT NULL,
`role` enum('administrateur','participant','en_lecture_seule') NOT NULL DEFAULT 'participant',
`date_ajout` timestamp NOT NULL DEFAULT current_timestamp(),
`notification_activee` tinyint(1) unsigned NOT NULL DEFAULT 1,
`last_read_message_id` varchar(50) DEFAULT NULL,
`via_target` tinyint(1) unsigned NOT NULL DEFAULT 0,
`can_reply` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uc_room_user` (`id_room`,`id_user`),
KEY `idx_room` (`id_room`),
KEY `idx_user` (`id_user`),
KEY `idx_anonymous_id` (`anonymous_id`),
KEY `idx_participants_active` (`id_room`,`id_user`,`notification_activee`),
CONSTRAINT `fk_chat_participants_room` FOREIGN KEY (`id_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_read_messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`fk_message` varchar(50) NOT NULL,
`fk_user` int(10) unsigned NOT NULL,
`date_read` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uc_message_user` (`fk_message`,`fk_user`),
KEY `idx_message` (`fk_message`),
KEY `idx_user` (`fk_user`),
CONSTRAINT `fk_chat_read_messages_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_rooms` (
`id` varchar(50) NOT NULL,
`type` enum('privee','groupe','liste_diffusion','broadcast','announcement') NOT NULL,
`title` varchar(100) DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
`fk_user` int(10) unsigned NOT NULL,
`fk_entite` int(10) unsigned DEFAULT NULL,
`statut` enum('active','archive') NOT NULL DEFAULT 'active',
`description` text DEFAULT NULL,
`reply_permission` enum('all','admins_only','sender_only','none') NOT NULL DEFAULT 'all',
`is_pinned` tinyint(1) unsigned NOT NULL DEFAULT 0,
`expiry_date` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_user` (`fk_user`),
KEY `idx_entite` (`fk_entite`),
KEY `idx_type` (`type`),
KEY `idx_statut` (`statut`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `email_counter` (
`id` int(10) unsigned NOT NULL DEFAULT 1,
`hour_start` timestamp NULL DEFAULT NULL,
`count` int(10) unsigned DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `email_queue` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_pass` int(10) unsigned NOT NULL DEFAULT 0,
`to_email` varchar(255) DEFAULT NULL,
`subject` varchar(255) DEFAULT NULL,
`body` text DEFAULT NULL,
`headers` text DEFAULT NULL,
`created_at` timestamp NULL DEFAULT current_timestamp(),
`status` enum('pending','sent','failed') DEFAULT 'pending',
`attempts` int(10) unsigned DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `entites` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`encrypted_name` varchar(255) DEFAULT NULL,
`adresse1` varchar(45) DEFAULT '',
`adresse2` varchar(45) DEFAULT '',
`code_postal` varchar(5) DEFAULT '',
`ville` varchar(45) DEFAULT '',
`fk_region` int(10) unsigned DEFAULT NULL,
`fk_type` int(10) unsigned DEFAULT 1,
`encrypted_phone` varchar(128) DEFAULT '',
`encrypted_mobile` varchar(128) DEFAULT '',
`encrypted_email` varchar(255) DEFAULT '',
`gps_lat` varchar(20) NOT NULL DEFAULT '',
`gps_lng` varchar(20) NOT NULL DEFAULT '',
`chk_stripe` tinyint(1) unsigned DEFAULT 0,
`encrypted_stripe_id` varchar(255) DEFAULT '',
`encrypted_iban` varchar(255) DEFAULT '',
`encrypted_bic` varchar(128) DEFAULT '',
`chk_demo` tinyint(1) unsigned DEFAULT 1,
`chk_mdp_manuel` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT 'Gestion des mots de passe manuelle O/N',
`chk_copie_mail_recu` tinyint(1) unsigned NOT NULL DEFAULT 0,
`chk_accept_sms` tinyint(1) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
KEY `entites_ibfk_1` (`fk_region`),
KEY `entites_ibfk_2` (`fk_type`),
CONSTRAINT `entites_ibfk_1` FOREIGN KEY (`fk_region`) REFERENCES `x_regions` (`id`) ON UPDATE CASCADE,
CONSTRAINT `entites_ibfk_2` FOREIGN KEY (`fk_type`) REFERENCES `x_entites_types` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1230 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `medias` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`support` varchar(45) NOT NULL DEFAULT '',
`support_id` int(10) unsigned NOT NULL DEFAULT 0,
`fichier` varchar(250) NOT NULL DEFAULT '',
`description` varchar(100) NOT NULL DEFAULT '',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=176 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_pass` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_sector` int(10) unsigned DEFAULT 0,
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
`fk_adresse` varchar(25) DEFAULT '' COMMENT 'adresses.cp??.id',
`passed_at` timestamp NULL DEFAULT NULL COMMENT 'Date du passage',
`fk_type` int(10) unsigned DEFAULT 0,
`numero` varchar(10) NOT NULL DEFAULT '',
`rue` varchar(75) NOT NULL DEFAULT '',
`rue_bis` varchar(1) NOT NULL DEFAULT '',
`ville` varchar(75) NOT NULL DEFAULT '',
`fk_habitat` int(10) unsigned DEFAULT 1,
`appt` varchar(5) DEFAULT '',
`niveau` varchar(5) DEFAULT '',
`residence` varchar(75) DEFAULT '',
`gps_lat` varchar(20) NOT NULL DEFAULT '',
`gps_lng` varchar(20) NOT NULL DEFAULT '',
`encrypted_name` varchar(255) NOT NULL DEFAULT '',
`montant` decimal(7,2) NOT NULL DEFAULT 0.00,
`fk_type_reglement` int(10) unsigned DEFAULT 1,
`remarque` text DEFAULT '',
`encrypted_email` varchar(255) DEFAULT '',
`nom_recu` varchar(50) DEFAULT NULL,
`date_recu` timestamp NULL DEFAULT NULL COMMENT 'Date de réception',
`date_creat_recu` timestamp NULL DEFAULT NULL COMMENT 'Date de création du reçu',
`date_sent_recu` timestamp NULL DEFAULT NULL COMMENT 'Date envoi du reçu',
`email_erreur` varchar(30) DEFAULT '',
`chk_email_sent` tinyint(1) unsigned NOT NULL DEFAULT 0,
`encrypted_phone` varchar(128) NOT NULL DEFAULT '',
`is_striped` tinyint(1) unsigned NOT NULL DEFAULT 0,
`docremis` tinyint(1) unsigned DEFAULT 0,
`date_repasser` timestamp NULL DEFAULT NULL COMMENT 'Date prévue pour repasser',
`nb_passages` int(11) DEFAULT 1 COMMENT 'Nb passages pour les a repasser',
`chk_gps_maj` tinyint(1) unsigned DEFAULT 0,
`chk_map_create` tinyint(1) unsigned DEFAULT 0,
`chk_mobile` tinyint(1) unsigned DEFAULT 0,
`chk_synchro` tinyint(1) unsigned DEFAULT 1 COMMENT 'chk synchro entre web et appli',
`chk_api_adresse` tinyint(1) unsigned DEFAULT 0,
`chk_maj_adresse` tinyint(1) unsigned DEFAULT 0,
`anomalie` tinyint(1) unsigned DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `fk_operation` (`fk_operation`),
KEY `fk_sector` (`fk_sector`),
KEY `fk_user` (`fk_user`),
KEY `fk_type` (`fk_type`),
KEY `fk_type_reglement` (`fk_type_reglement`),
KEY `email` (`encrypted_email`),
CONSTRAINT `ope_pass_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_pass_ibfk_2` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_pass_ibfk_3` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_pass_ibfk_4` FOREIGN KEY (`fk_type_reglement`) REFERENCES `x_types_reglements` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=19499566 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_pass_histo` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_pass` int(10) unsigned NOT NULL DEFAULT 0,
`date_histo` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date historique',
`sujet` varchar(50) DEFAULT NULL,
`remarque` varchar(250) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `ope_pass_histo_fk_pass_IDX` (`fk_pass`) USING BTREE,
KEY `ope_pass_histo_date_histo_IDX` (`date_histo`) USING BTREE,
CONSTRAINT `ope_pass_histo_ibfk_1` FOREIGN KEY (`fk_pass`) REFERENCES `ope_pass` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6752 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_sectors` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_old_sector` int(10) unsigned DEFAULT NULL,
`libelle` varchar(75) NOT NULL DEFAULT '',
`sector` text NOT NULL DEFAULT '',
`color` varchar(7) NOT NULL DEFAULT '#4B77BE',
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `fk_operation` (`fk_operation`),
CONSTRAINT `ope_sectors_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=27675 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `ope_users_ibfk_1` (`fk_operation`),
KEY `ope_users_ibfk_2` (`fk_user`),
CONSTRAINT `ope_users_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_users_ibfk_2` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=199006 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_users_sectors` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
`fk_sector` int(10) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `fk_operation` (`fk_operation`),
KEY `fk_user` (`fk_user`),
KEY `fk_sector` (`fk_sector`),
CONSTRAINT `ope_users_sectors_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_users_sectors_ibfk_2` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_users_sectors_ibfk_3` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=48082 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_users_suivis` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
`date_suivi` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date du suivi',
`gps_lat` varchar(20) NOT NULL DEFAULT '',
`gps_lng` varchar(20) NOT NULL DEFAULT '',
`vitesse` varchar(20) NOT NULL DEFAULT '',
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `operations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_entite` int(10) unsigned NOT NULL DEFAULT 1,
`libelle` varchar(75) NOT NULL DEFAULT '',
`date_deb` date NOT NULL DEFAULT '0000-00-00',
`date_fin` date NOT NULL DEFAULT '0000-00-00',
`chk_distinct_sectors` tinyint(1) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `fk_entite` (`fk_entite`),
KEY `date_deb` (`date_deb`),
CONSTRAINT `operations_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3121 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `params` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(35) NOT NULL DEFAULT '',
`valeur` varchar(255) NOT NULL DEFAULT '',
`aide` varchar(150) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `sectors_adresses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_adresse` varchar(25) DEFAULT NULL COMMENT 'adresses.cp??.id',
`osm_id` int(10) unsigned NOT NULL DEFAULT 0,
`fk_sector` int(10) unsigned NOT NULL DEFAULT 0,
`osm_name` varchar(50) NOT NULL DEFAULT '',
`numero` varchar(5) NOT NULL DEFAULT '',
`rue_bis` varchar(5) NOT NULL DEFAULT '',
`rue` varchar(60) NOT NULL DEFAULT '',
`cp` varchar(5) NOT NULL DEFAULT '',
`ville` varchar(60) NOT NULL DEFAULT '',
`gps_lat` varchar(20) NOT NULL DEFAULT '',
`gps_lng` varchar(20) NOT NULL DEFAULT '',
`osm_date_creat` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
PRIMARY KEY (`id`),
KEY `sectors_adresses_fk_sector_index` (`fk_sector`),
KEY `sectors_adresses_numero_index` (`numero`),
KEY `sectors_adresses_rue_index` (`rue`),
KEY `sectors_adresses_ville_index` (`ville`),
CONSTRAINT `sectors_adresses_ibfk_1` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1562946 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_entite` int(10) unsigned DEFAULT 1,
`fk_role` int(10) unsigned DEFAULT 1,
`fk_titre` int(10) unsigned DEFAULT 1,
`encrypted_name` varchar(255) DEFAULT NULL,
`first_name` varchar(45) DEFAULT NULL,
`sect_name` varchar(60) DEFAULT '',
`encrypted_user_name` varchar(128) DEFAULT '',
`user_pass_hash` varchar(60) DEFAULT NULL,
`encrypted_phone` varchar(128) DEFAULT NULL,
`encrypted_mobile` varchar(128) DEFAULT NULL,
`encrypted_email` varchar(255) DEFAULT '',
`chk_alert_email` tinyint(1) unsigned DEFAULT 1,
`chk_suivi` tinyint(1) unsigned DEFAULT 0,
`date_naissance` date DEFAULT NULL,
`date_embauche` date DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
KEY `fk_entite` (`fk_entite`),
KEY `username` (`encrypted_user_name`),
KEY `users_ibfk_2` (`fk_role`),
KEY `users_ibfk_3` (`fk_titre`),
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE,
CONSTRAINT `users_ibfk_2` FOREIGN KEY (`fk_role`) REFERENCES `x_users_roles` (`id`) ON UPDATE CASCADE,
CONSTRAINT `users_ibfk_3` FOREIGN KEY (`fk_titre`) REFERENCES `x_users_titres` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=10027748 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_departements` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(3) DEFAULT NULL,
`fk_region` int(10) unsigned DEFAULT 1,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_departements_ibfk_1` (`fk_region`),
CONSTRAINT `x_departements_ibfk_1` FOREIGN KEY (`fk_region`) REFERENCES `x_regions` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=105 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_devises` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(3) DEFAULT NULL,
`symbole` varchar(6) DEFAULT NULL,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_entites_types` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_pays` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(3) DEFAULT NULL,
`fk_continent` int(10) unsigned DEFAULT NULL,
`fk_devise` int(10) unsigned DEFAULT 1,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_pays_ibfk_1` (`fk_devise`),
CONSTRAINT `x_pays_ibfk_1` FOREIGN KEY (`fk_devise`) REFERENCES `x_devises` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Table des pays avec leurs codes' `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_regions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_pays` int(10) unsigned DEFAULT 1,
`libelle` varchar(45) DEFAULT NULL,
`libelle_long` varchar(45) DEFAULT NULL,
`table_osm` varchar(45) DEFAULT NULL,
`departements` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_regions_ibfk_1` (`fk_pays`),
CONSTRAINT `x_regions_ibfk_1` FOREIGN KEY (`fk_pays`) REFERENCES `x_pays` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_types_passages` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(10) DEFAULT NULL,
`color_button` varchar(15) DEFAULT NULL,
`color_mark` varchar(15) DEFAULT NULL,
`color_table` varchar(15) DEFAULT NULL,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_types_reglements` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_users_roles` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Les différents rôles des utilisateurs' `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_users_titres` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Les différents titres des utilisateurs' `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_villes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_departement` int(10) unsigned DEFAULT 1,
`libelle` varchar(65) DEFAULT NULL,
`code_postal` varchar(5) DEFAULT NULL,
`code_insee` varchar(5) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_villes_ibfk_1` (`fk_departement`),
CONSTRAINT `x_villes_ibfk_1` FOREIGN KEY (`fk_departement`) REFERENCES `x_departements` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=38950 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `z_sessions` (
`sid` text NOT NULL,
`fk_user` int(11) NOT NULL,
`role` varchar(10) DEFAULT NULL,
`date_modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`ip` varchar(50) NOT NULL,
`browser` varchar(150) NOT NULL,
`data` mediumtext DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `chat_conversations_unread` AS select `r`.`id` AS `id`,`r`.`type` AS `type`,`r`.`title` AS `title`,`r`.`date_creation` AS `date_creation`,`r`.`fk_user` AS `fk_user`,`r`.`fk_entite` AS `fk_entite`,`r`.`statut` AS `statut`,`r`.`description` AS `description`,`r`.`reply_permission` AS `reply_permission`,`r`.`is_pinned` AS `is_pinned`,`r`.`expiry_date` AS `expiry_date`,`r`.`updated_at` AS `updated_at`,count(distinct `m`.`id`) AS `total_messages`,count(distinct `rm`.`id`) AS `read_messages`,count(distinct `m`.`id`) - count(distinct `rm`.`id`) AS `unread_messages`,(select `geo_app`.`chat_messages`.`date_sent` from `chat_messages` where `geo_app`.`chat_messages`.`fk_room` = `r`.`id` order by `geo_app`.`chat_messages`.`date_sent` desc limit 1) AS `last_message_date` from ((`chat_rooms` `r` left join `chat_messages` `m` on(`r`.`id` = `m`.`fk_room`)) left join `chat_read_messages` `rm` on(`m`.`id` = `rm`.`fk_message`)) group by `r`.`id`;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

View File

@@ -386,9 +386,10 @@ class EntiteController {
* Met à jour une entité existante avec les données fournies * Met à jour une entité existante avec les données fournies
* Seuls les administrateurs (rôle > 2) peuvent modifier certains champs * Seuls les administrateurs (rôle > 2) peuvent modifier certains champs
* *
* @param string $id ID de l'entité depuis l'URL
* @return void * @return void
*/ */
public function updateEntite(): void { public function updateEntite(string $id = null): void {
try { try {
// Vérifier l'authentification et les droits d'accès // Vérifier l'authentification et les droits d'accès
$userId = Session::getUserId(); $userId = Session::getUserId();
@@ -419,7 +420,10 @@ class EntiteController {
// Récupérer les données de la requête // Récupérer les données de la requête
$data = Request::getJson(); $data = Request::getJson();
if (!isset($data['id']) || empty($data['id'])) { // Priorité à l'ID de l'URL, sinon utiliser celui du JSON
$entiteId = $id ? (int)$id : (isset($data['id']) ? (int)$data['id'] : null);
if (!$entiteId) {
Response::json([ Response::json([
'status' => 'error', 'status' => 'error',
'message' => 'ID de l\'entité requis' 'message' => 'ID de l\'entité requis'
@@ -427,8 +431,6 @@ class EntiteController {
return; return;
} }
$entiteId = (int)$data['id'];
// Récupérer les données actuelles de l'entité pour vérifier si l'adresse a changé // Récupérer les données actuelles de l'entité pour vérifier si l'adresse a changé
$stmt = $this->db->prepare('SELECT adresse1, adresse2, code_postal, ville FROM entites WHERE id = ?'); $stmt = $this->db->prepare('SELECT adresse1, adresse2, code_postal, ville FROM entites WHERE id = ?');
$stmt->execute([$entiteId]); $stmt->execute([$entiteId]);

View File

@@ -43,6 +43,7 @@ class Router {
$this->get('entites', ['EntiteController', 'getEntites']); $this->get('entites', ['EntiteController', 'getEntites']);
$this->get('entites/:id', ['EntiteController', 'getEntiteById']); $this->get('entites/:id', ['EntiteController', 'getEntiteById']);
$this->get('entites/postal/:code', ['EntiteController', 'getEntiteByPostalCode']); $this->get('entites/postal/:code', ['EntiteController', 'getEntiteByPostalCode']);
$this->put('entites/:id', ['EntiteController', 'updateEntite']);
// Routes villes // Routes villes
$this->get('villes', ['VilleController', 'searchVillesByPostalCode']); $this->get('villes', ['VilleController', 'searchVillesByPostalCode']);
@@ -187,7 +188,8 @@ class Router {
error_log("Routes disponibles pour $method: " . implode(', ', array_keys($this->routes[$method]))); error_log("Routes disponibles pour $method: " . implode(', ', array_keys($this->routes[$method])));
foreach ($this->routes[$method] as $route => $handler) { foreach ($this->routes[$method] as $route => $handler) {
$pattern = preg_replace('/{[^}]+}/', '([^/]+)', $route); // Correction: utiliser :param au lieu de {param}
$pattern = preg_replace('/:([^\/]+)/', '([^/]+)', $route);
$pattern = "@^" . $pattern . "$@D"; $pattern = "@^" . $pattern . "$@D";
error_log("Test pattern: $pattern contre uri: $uri"); error_log("Test pattern: $pattern contre uri: $uri");

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
class AnonymousUserModelAdapter extends TypeAdapter<AnonymousUserModel> { class AnonymousUserModelAdapter extends TypeAdapter<AnonymousUserModel> {
@override @override
final int typeId = 24; final int typeId = 23;
@override @override
AnonymousUserModel read(BinaryReader reader) { AnonymousUserModel read(BinaryReader reader) {

View File

@@ -4,7 +4,7 @@
class AudienceTargetModelAdapter extends TypeAdapter<AudienceTargetModel> { class AudienceTargetModelAdapter extends TypeAdapter<AudienceTargetModel> {
@override @override
final int typeId = 23; final int typeId = 24;
@override @override
AudienceTargetModel read(BinaryReader reader) { AudienceTargetModel read(BinaryReader reader) {

View File

@@ -32,13 +32,16 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
chkCopieMailRecu: fields[16] as bool?, chkCopieMailRecu: fields[16] as bool?,
chkAcceptSms: fields[17] as bool?, chkAcceptSms: fields[17] as bool?,
chkActive: fields[18] as bool?, chkActive: fields[18] as bool?,
chkStripe: fields[19] as bool?,
createdAt: fields[20] as DateTime?,
updatedAt: fields[21] as DateTime?,
); );
} }
@override @override
void write(BinaryWriter writer, ClientModel obj) { void write(BinaryWriter writer, ClientModel obj) {
writer writer
..writeByte(19) ..writeByte(22)
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
@@ -76,7 +79,13 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
..writeByte(17) ..writeByte(17)
..write(obj.chkAcceptSms) ..write(obj.chkAcceptSms)
..writeByte(18) ..writeByte(18)
..write(obj.chkActive); ..write(obj.chkActive)
..writeByte(19)
..write(obj.chkStripe)
..writeByte(20)
..write(obj.createdAt)
..writeByte(21)
..write(obj.updatedAt);
} }
@override @override

View File

@@ -14,48 +14,57 @@ class MembreModelAdapter extends TypeAdapter<MembreModel> {
}; };
return MembreModel( return MembreModel(
id: fields[0] as int, id: fields[0] as int,
fkRole: fields[1] as int, fkEntite: fields[1] as int?,
fkTitre: fields[2] as int, role: fields[2] as int,
firstName: fields[3] as String, fkTitre: fields[3] as int?,
sectName: fields[4] as String?, name: fields[4] as String?,
dateNaissance: fields[5] as DateTime?, firstName: fields[5] as String?,
dateEmbauche: fields[6] as DateTime?, username: fields[6] as String?,
chkActive: fields[7] as int, sectName: fields[7] as String?,
name: fields[8] as String, email: fields[8] as String,
username: fields[9] as String, phone: fields[9] as String?,
email: fields[10] as String, mobile: fields[10] as String?,
fkEntite: fields[11] as int, dateNaissance: fields[11] as DateTime?,
dateEmbauche: fields[12] as DateTime?,
createdAt: fields[13] as DateTime,
isActive: fields[14] as bool,
); );
} }
@override @override
void write(BinaryWriter writer, MembreModel obj) { void write(BinaryWriter writer, MembreModel obj) {
writer writer
..writeByte(12) ..writeByte(15)
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
..write(obj.fkRole) ..write(obj.fkEntite)
..writeByte(2) ..writeByte(2)
..write(obj.fkTitre) ..write(obj.role)
..writeByte(3) ..writeByte(3)
..write(obj.firstName) ..write(obj.fkTitre)
..writeByte(4) ..writeByte(4)
..write(obj.sectName)
..writeByte(5)
..write(obj.dateNaissance)
..writeByte(6)
..write(obj.dateEmbauche)
..writeByte(7)
..write(obj.chkActive)
..writeByte(8)
..write(obj.name) ..write(obj.name)
..writeByte(9) ..writeByte(5)
..write(obj.firstName)
..writeByte(6)
..write(obj.username) ..write(obj.username)
..writeByte(10) ..writeByte(7)
..write(obj.sectName)
..writeByte(8)
..write(obj.email) ..write(obj.email)
..writeByte(9)
..write(obj.phone)
..writeByte(10)
..write(obj.mobile)
..writeByte(11) ..writeByte(11)
..write(obj.fkEntite); ..write(obj.dateNaissance)
..writeByte(12)
..write(obj.dateEmbauche)
..writeByte(13)
..write(obj.createdAt)
..writeByte(14)
..write(obj.isActive);
} }
@override @override

View File

@@ -4,7 +4,7 @@
class UserSectorModelAdapter extends TypeAdapter<UserSectorModel> { class UserSectorModelAdapter extends TypeAdapter<UserSectorModel> {
@override @override
final int typeId = 7; final int typeId = 6;
@override @override
UserSectorModel read(BinaryReader reader) { UserSectorModel read(BinaryReader reader) {

View File

@@ -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.

View File

@@ -1 +0,0 @@
{"version":2,"entries":[{"package":"geosector_app","rootUri":"../","packageUri":"lib/"}]}

File diff suppressed because one or more lines are too long

View File

@@ -1780,6 +1780,10 @@ file:///Users/pierre/dev/geosector/app/lib/core/repositories/user_repository.dar
file:///Users/pierre/dev/geosector/app/lib/core/services/api_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/api_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/app_info_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/app_info_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/connectivity_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/connectivity_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/current_amicale_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/current_user_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/data_loading_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/hive_adapters.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/hive_reset_state_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/hive_reset_state_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart file:///Users/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/location_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/location_service.dart
@@ -1823,7 +1827,6 @@ file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/custom_text_fiel
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_app_bar.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_app_bar.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_layout.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_layout.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/help_dialog.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/help_dialog.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/loading_overlay.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/loading_progress_overlay.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/loading_progress_overlay.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/mapbox_map.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/mapbox_map.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget.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

View File

@@ -1779,6 +1779,10 @@ file:///Users/pierre/dev/geosector/app/lib/core/repositories/user_repository.dar
file:///Users/pierre/dev/geosector/app/lib/core/services/api_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/api_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/app_info_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/app_info_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/connectivity_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/connectivity_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/current_amicale_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/current_user_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/data_loading_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/hive_adapters.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/hive_reset_state_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/hive_reset_state_service.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart file:///Users/pierre/dev/geosector/app/lib/core/services/hive_web_fix.dart
file:///Users/pierre/dev/geosector/app/lib/core/services/location_service.dart file:///Users/pierre/dev/geosector/app/lib/core/services/location_service.dart
@@ -1822,7 +1826,6 @@ file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/custom_text_fiel
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_app_bar.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_app_bar.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_layout.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/dashboard_layout.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/help_dialog.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/help_dialog.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/loading_overlay.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/loading_progress_overlay.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/loading_progress_overlay.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/mapbox_map.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/mapbox_map.dart
file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget.dart file:///Users/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget.dart

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -29,9 +29,9 @@
## 🚀 SESSION DE TRAVAIL - PLANNING DÉTAILLÉ ## 🚀 SESSION DE TRAVAIL - PLANNING DÉTAILLÉ
### Phase 0: Préparation Git et environnement (15 min) ### Phase 0: Préparation Git et environnement (15 min)
#### Tâche 0.1: Création de la branche (5 min) #### Tâche 0.1: Création de la branche (5 min)
```bash ```bash
# Créer et basculer sur la nouvelle branche # Créer et basculer sur la nouvelle branche
@@ -41,7 +41,7 @@ git checkout -b singletons
git branch git branch
``` ```
#### Tâche 0.2: Backup et documentation (10 min) #### Tâche 0.2: Backup et documentation (10 min)
```bash ```bash
# Créer un backup des fichiers critiques # Créer un backup des fichiers critiques
@@ -72,9 +72,9 @@ git commit -m "feat: création branche singletons - début refactorisation
- Objectif: renommer Box users -> user" - Objectif: renommer Box users -> user"
``` ```
### Phase 1: Préparation et analyse (45 min) ### Phase 1: Préparation et analyse (45 min)
#### Tâche 1.1: Audit du code existant (20 min) #### Tâche 1.1: Audit du code existant (20 min)
```bash ```bash
# Rechercher toutes les utilisations des services # Rechercher toutes les utilisations des services
@@ -94,7 +94,7 @@ grep -r "usersBoxName" app/lib --include="*.dart" > audit_usersbox.txt
- [x] Analyser les Box Hive users/amicale - [x] Analyser les Box Hive users/amicale
- [x] Identifier toutes les occurrences de "usersBoxName" - [x] Identifier toutes les occurrences de "usersBoxName"
#### Tâche 1.2: Modification app_keys.dart pour renommage Box (10 min) #### Tâche 1.2: Modification app_keys.dart pour renommage Box (10 min)
**Fichier à modifier** : `app/lib/core/constants/app_keys.dart` **Fichier à modifier** : `app/lib/core/constants/app_keys.dart`
**Actions** : **Actions** :
@@ -103,7 +103,7 @@ grep -r "usersBoxName" app/lib --include="*.dart" > audit_usersbox.txt
- [x] Ajouter une constante de migration si nécessaire - [x] Ajouter une constante de migration si nécessaire
- [x] Documenter le changement - [x] Documenter le changement
**Code à modifier** : **Code modifié** :
```dart ```dart
// Avant // Avant
@@ -114,28 +114,83 @@ static const String userBoxName = 'user'; // Box pour l'utilisateur unique conne
static const String usersBoxNameOld = 'users'; // Pour migration si nécessaire static const String usersBoxNameOld = 'users'; // Pour migration si nécessaire
``` ```
#### Tâche 1.3: Planification de la refactorisation (15 min) #### Tâche 1.3: Planification de la refactorisation (15 min)
**Actions** : **Actions** :
- [ ] Créer une liste des repositories à modifier - [x] Créer une liste des repositories à modifier
- [ ] Identifier les pages/widgets accédant aux données utilisateur - [x] Identifier les pages/widgets accédant aux données utilisateur
- [ ] Planifier l'ordre de modification (dépendances) - [x] Planifier l'ordre de modification (dépendances)
- [ ] Préparer la stratégie de tests - [x] Préparer la stratégie de tests
- [ ] Définir l'architecture des nouveaux services - [x] Définir l'architecture des nouveaux services
- [ ] Planifier la migration de la Box users -> user - [x] Planifier la migration de la Box users -> user
--- ---
### Phase 2: Renommage et migration de la Hive Box (30 min) ### Phase 2: Correction des modèles et préparation (60 min)
#### Tâche 2.1: Mise à jour main.dart pour la nouvelle Box (15 min) #### Tâche 2.1: Correction MembreModel selon les vrais champs (30 min)
**Fichier modifié** : `app/lib/core/data/models/membre_model.dart`
**Actions** :
- [x] Corriger les champs selon les spécifications réelles :
- `final int id`
- `int? fkEntite`
- `final int role`
- `int? fkTitre`
- `String? name`
- `String? firstName`
- `String? username`
- `String? sectName`
- `final String email`
- `String? phone`
- `String? mobile`
- `DateTime? dateNaissance`
- `DateTime? dateEmbauche`
- `final DateTime createdAt`
- `bool isActive`
- [x] Adapter les annotations Hive @HiveField
- [x] Corriger fromJson() et toJson()
- [x] Mettre à jour copyWith()
#### Tâche 2.2: Correction ClientModel avec champs manquants (15 min) ✅
**Fichier modifié** : `app/lib/core/data/models/client_model.dart`
**Actions** :
- [x] Ajouter les champs manqués : `chkStripe`, `createdAt`, `updatedAt`
- [x] Mettre à jour les annotations Hive
- [x] Corriger fromJson() et toJson()
- [x] Mettre à jour copyWith()
#### Tâche 2.3: Correction des repositories selon les vrais modèles (15 min) ✅
**Fichiers modifiés** :
- `app/lib/core/repositories/membre_repository.dart`
- `app/lib/core/repositories/client_repository.dart`
**Actions** :
- [x] Adapter MembreRepository pour les vrais champs (`isActive` au lieu de `chkActive`, etc.)
- [x] Corriger les méthodes create/update pour éviter les reconstructions manuelles
- [x] Utiliser copyWith() correctement
- [x] Simplifier la logique de création API
- [x] Corriger ClientRepository de la même manière
---
### Phase 3: Renommage et migration de la Hive Box (30 min)
#### Tâche 3.1: Mise à jour main.dart pour la nouvelle Box (15 min)
**Fichier à modifier** : `app/lib/main.dart` **Fichier à modifier** : `app/lib/main.dart`
**Actions** : **Actions** :
- [x] Remplacer `AppKeys.usersBoxName` par `AppKeys.userBoxName` - [ ] Remplacer `AppKeys.usersBoxName` par `AppKeys.userBoxName`
- [x] Modifier l'ouverture de la Box dans `_openEssentialHiveBoxes()` - [ ] Modifier l'ouverture de la Box dans `_openEssentialHiveBoxes()`
- [ ] Ajouter logique de migration depuis l'ancienne Box si nécessaire - [ ] Ajouter logique de migration depuis l'ancienne Box si nécessaire
**Code à modifier** : **Code à modifier** :
@@ -170,19 +225,18 @@ Future<void> _openEssentialHiveBoxes() async {
} }
} catch (e) { } catch (e) {
debugPrint('⚠️ Erreur migration box users: $e'); debugPrint('⚠️ Erreur migration box users: $e');
}
} }
}
``` ```
#### Tâche 2.2: Mise à jour UserRepository pour la nouvelle Box (15 min) #### Tâche 3.2: Mise à jour UserRepository pour la nouvelle Box (15 min)
**Fichier à modifier** : `app/lib/core/repositories/user_repository.dart` **Fichier à modifier** : `app/lib/core/repositories/user_repository.dart`
**Actions** : **Actions** :
- [x] Remplacer toutes les occurrences de `AppKeys.usersBoxName` par `AppKeys.userBoxName` - [ ] Remplacer toutes les occurrences de `AppKeys.usersBoxName` par `AppKeys.userBoxName`
- [x] Modifier les getters de Box - [ ] Modifier les getters de Box
- [x] Tester que la compilation passe - [ ] Tester que la compilation passe
**Code à modifier** : **Code à modifier** :
@@ -196,16 +250,16 @@ Box<UserModel> get _userBox => Hive.box<UserModel>(AppKeys.userBoxName);
--- ---
### Phase 3: Création du nouveau ApiService Singleton (45 min) ### Phase 4: Création du nouveau ApiService Singleton (45 min)
#### Tâche 3.1: Backup du code existant (5 min) #### Tâche 4.1: Backup du code existant (5 min)
```bash ```bash
# Sauvegarder l'ApiService actuel # Sauvegarder l'ApiService actuel
cp app/lib/core/services/api_service.dart app/lib/core/services/api_service_backup.dart cp app/lib/core/services/api_service.dart app/lib/core/services/api_service_backup.dart
``` ```
#### Tâche 3.2: Refactorisation ApiService en Singleton (40 min) #### Tâche 4.2: Refactorisation ApiService en Singleton (40 min)
**Fichier à modifier** : `app/lib/core/services/api_service.dart` **Fichier à modifier** : `app/lib/core/services/api_service.dart`
**Actions** : **Actions** :
@@ -287,9 +341,9 @@ class ApiService {
--- ---
### Phase 4: Création des Services Singleton Utilisateur/Amicale (90 min) ### Phase 5: Création des Services Singleton Utilisateur/Amicale (90 min)
#### Tâche 4.1: Création CurrentUserService (45 min) #### Tâche 5.1: Création CurrentUserService (45 min)
**Fichier à créer** : `app/lib/core/services/current_user_service.dart` **Fichier à créer** : `app/lib/core/services/current_user_service.dart`
**Actions** : **Actions** :
@@ -457,7 +511,7 @@ class CurrentUserService extends ChangeNotifier {
} }
``` ```
#### Tâche 4.2: Création CurrentAmicaleService (45 min) #### Tâche 5.2: Création CurrentAmicaleService (45 min)
**Fichier à créer** : `app/lib/core/services/current_amicale_service.dart` **Fichier à créer** : `app/lib/core/services/current_amicale_service.dart`
**Actions** : **Actions** :
@@ -619,9 +673,9 @@ class CurrentAmicaleService extends ChangeNotifier {
--- ---
### Phase 5: Modification du main.dart (20 min) ### Phase 6: Modification du main.dart (20 min)
#### Tâche 5.1: Intégration dans main.dart (20 min) #### Tâche 6.1: Intégration dans main.dart (20 min)
**Fichier à modifier** : `app/lib/main.dart` **Fichier à modifier** : `app/lib/main.dart`
**Actions** : **Actions** :
@@ -668,9 +722,9 @@ import 'package:geosector_app/core/services/current_amicale_service.dart';
--- ---
### Phase 6: Commit intermédiaire de sécurité (10 min) ### Phase 7: Commit intermédiaire de sécurité (10 min)
#### Tâche 6.1: Commit des services créés (10 min) #### Tâche 7.1: Commit des services créés (10 min)
```bash ```bash
# Ajouter tous les nouveaux fichiers # Ajouter tous les nouveaux fichiers
@@ -704,9 +758,9 @@ Box Hive:
--- ---
### Phase 7: Modification de l'App principale (20 min) ### Phase 8: Modification de l'App principale (20 min)
#### Tâche 7.1: Refactorisation app.dart (20 min) #### Tâche 8.1: Refactorisation app.dart (20 min)
**Fichier à modifier** : `app/lib/app.dart` **Fichier à modifier** : `app/lib/app.dart`
@@ -734,9 +788,9 @@ final userRepository = UserRepository(apiService);
--- ---
### Phase 8: Refactorisation des Repositories (90 min) ### Phase 9: Refactorisation des Repositories (90 min)
#### Tâche 8.1: UserRepository - Refactorisation majeure (40 min) #### Tâche 9.1: UserRepository - Refactorisation majeure (40 min)
**Fichier** : `app/lib/core/repositories/user_repository.dart` **Fichier** : `app/lib/core/repositories/user_repository.dart`
@@ -855,7 +909,7 @@ class UserRepository extends ChangeNotifier {
} }
``` ```
#### Tâche 8.2: AmicaleRepository (20 min) #### Tâche 9.2: AmicaleRepository (20 min)
**Fichier** : `app/lib/core/repositories/amicale_repository.dart` **Fichier** : `app/lib/core/repositories/amicale_repository.dart`
@@ -866,7 +920,7 @@ class UserRepository extends ChangeNotifier {
- [ ] Simplifier l'accès à l'amicale courante - [ ] Simplifier l'accès à l'amicale courante
- [ ] Intégrer avec CurrentAmicaleService - [ ] Intégrer avec CurrentAmicaleService
#### Tâche 8.3: MembreRepository (15 min) #### Tâche 9.3: MembreRepository (15 min)
**Fichier** : `app/lib/core/repositories/membre_repository.dart` **Fichier** : `app/lib/core/repositories/membre_repository.dart`
@@ -876,7 +930,7 @@ class UserRepository extends ChangeNotifier {
- [ ] Utiliser `ApiService.instance` dans les méthodes - [ ] Utiliser `ApiService.instance` dans les méthodes
- [ ] Utiliser CurrentUserService pour les vérifications de permissions - [ ] Utiliser CurrentUserService pour les vérifications de permissions
#### Tâche 8.4: Autres repositories (15 min) #### Tâche 9.4: Autres repositories (15 min)
**Fichiers à traiter** : **Fichiers à traiter** :
@@ -893,9 +947,9 @@ class UserRepository extends ChangeNotifier {
--- ---
### Phase 9: Modification des Pages principales (120 min) ### Phase 10: Modification des Pages principales (120 min)
#### Tâche 9.1: Pages d'authentification (30 min) #### Tâche 10.1: Pages d'authentification (30 min)
**Fichiers** : **Fichiers** :
@@ -954,7 +1008,7 @@ class _LoginPageState extends State<LoginPage> {
} }
``` ```
#### Tâche 9.2: Dashboard pages (30 min) #### Tâche 10.2: Dashboard pages (30 min)
**Fichiers** : **Fichiers** :
@@ -1041,7 +1095,7 @@ class AdminDashboardPage extends StatelessWidget {
} }
``` ```
#### Tâche 9.3: Pages de gestion (30 min) #### Tâche 10.3: Pages de gestion (30 min)
**Fichiers** : **Fichiers** :
@@ -1055,7 +1109,7 @@ class AdminDashboardPage extends StatelessWidget {
- [ ] Utiliser les services singleton - [ ] Utiliser les services singleton
- [ ] Simplifier l'accès aux données - [ ] Simplifier l'accès aux données
#### Tâche 9.4: Pages formulaires (30 min) #### Tâche 10.4: Pages formulaires (30 min)
**Fichiers contenant des formulaires** : **Fichiers contenant des formulaires** :
@@ -1071,9 +1125,9 @@ class AdminDashboardPage extends StatelessWidget {
--- ---
### Phase 10: Modification des Widgets (90 min) ### Phase 11: Modification des Widgets (90 min)
#### Tâche 10.1: Création de widgets d'information (30 min) #### Tâche 11.1: Création de widgets d'information (30 min)
**Nouveaux widgets à créer** : **Nouveaux widgets à créer** :
@@ -1209,7 +1263,7 @@ class AmicaleInfoWidget extends StatelessWidget {
} }
``` ```
#### Tâche 10.2: Widgets de tableaux (30 min) #### Tâche 11.2: Widgets de tableaux (30 min)
**Fichiers** : **Fichiers** :
@@ -1223,7 +1277,7 @@ class AmicaleInfoWidget extends StatelessWidget {
- [ ] Utiliser CurrentUserService pour les vérifications de permissions - [ ] Utiliser CurrentUserService pour les vérifications de permissions
- [ ] Simplifier la logique d'affichage conditionnel - [ ] Simplifier la logique d'affichage conditionnel
#### Tâche 10.3: Widgets de formulaires (30 min) #### Tâche 11.3: Widgets de formulaires (30 min)
**Fichiers** : **Fichiers** :
@@ -1238,9 +1292,9 @@ class AmicaleInfoWidget extends StatelessWidget {
--- ---
### Phase 11: Mise à jour du Router et Navigation (45 min) ### Phase 12: Mise à jour du Router et Navigation (45 min)
#### Tâche 11.1: Configuration GoRouter (30 min) #### Tâche 12.1: Configuration GoRouter (30 min)
**Fichier** : `app/lib/core/routing/app_router.dart` **Fichier** : `app/lib/core/routing/app_router.dart`
@@ -1275,7 +1329,7 @@ GoRoute(
) )
``` ```
#### Tâche 11.2: Création AuthGuard (15 min) #### Tâche 12.2: Création AuthGuard (15 min)
**Fichier à créer** : `app/lib/core/routing/auth_guard.dart` **Fichier à créer** : `app/lib/core/routing/auth_guard.dart`
@@ -1327,9 +1381,9 @@ class AuthGuard {
--- ---
### Phase 12: Tests et validation (60 min) ### Phase 13: Tests et validation (60 min)
#### Tâche 12.1: Tests de compilation (15 min) #### Tâche 13.1: Tests de compilation (15 min)
**Actions** : **Actions** :
@@ -1338,7 +1392,7 @@ class AuthGuard {
- [ ] Corriger erreurs de compilation - [ ] Corriger erreurs de compilation
- [ ] Vérifier warnings - [ ] Vérifier warnings
#### Tâche 12.2: Tests fonctionnels de base (30 min) #### Tâche 13.2: Tests fonctionnels de base (30 min)
**Scénarios à tester** : **Scénarios à tester** :
@@ -1350,7 +1404,7 @@ class AuthGuard {
- [ ] Persistence des données au redémarrage - [ ] Persistence des données au redémarrage
- [ ] Migration de la Box users -> user - [ ] Migration de la Box users -> user
#### Tâche 12.3: Tests des services singleton (15 min) #### Tâche 13.3: Tests des services singleton (15 min)
**Actions** : **Actions** :
@@ -1361,9 +1415,9 @@ class AuthGuard {
--- ---
### Phase 13: Tests unitaires (45 min) ### Phase 14: Tests unitaires (45 min)
#### Tâche 13.1: Tests ApiService (15 min) #### Tâche 14.1: Tests ApiService (15 min)
**Fichier** : `test/core/services/api_service_test.dart` **Fichier** : `test/core/services/api_service_test.dart`
@@ -1374,7 +1428,7 @@ class AuthGuard {
- [ ] Tester thread-safety - [ ] Tester thread-safety
- [ ] Mocker les appels réseau - [ ] Mocker les appels réseau
#### Tâche 13.2: Tests CurrentUserService (15 min) #### Tâche 14.2: Tests CurrentUserService (15 min)
**Fichier** : `test/core/services/current_user_service_test.dart` **Fichier** : `test/core/services/current_user_service_test.dart`
@@ -1385,7 +1439,7 @@ class AuthGuard {
- [ ] Tester persistence Hive avec nouvelle Box - [ ] Tester persistence Hive avec nouvelle Box
- [ ] Tester les getters de rôles - [ ] Tester les getters de rôles
#### Tâche 13.3: Tests CurrentAmicaleService (15 min) #### Tâche 14.3: Tests CurrentAmicaleService (15 min)
**Fichier** : `test/core/services/current_amicale_service_test.dart` **Fichier** : `test/core/services/current_amicale_service_test.dart`
@@ -1398,9 +1452,9 @@ class AuthGuard {
--- ---
### Phase 14: Optimisations et nettoyage (45 min) ### Phase 15: Optimisations et nettoyage (45 min)
#### Tâche 14.1: Optimisation des performances (20 min) #### Tâche 15.1: Optimisation des performances (20 min)
**Actions** : **Actions** :
@@ -1409,7 +1463,7 @@ class AuthGuard {
- [ ] Éliminer les rebuilds inutiles - [ ] Éliminer les rebuilds inutiles
- [ ] Vérifier les memory leaks - [ ] Vérifier les memory leaks
#### Tâche 14.2: Nettoyage du code (25 min) #### Tâche 15.2: Nettoyage du code (25 min)
**Actions** : **Actions** :
@@ -1421,9 +1475,9 @@ class AuthGuard {
--- ---
### Phase 15: Commit final et documentation (30 min) ### Phase 16: Commit final et documentation (30 min)
#### Tâche 15.1: Commit final (15 min) #### Tâche 16.1: Commit final (15 min)
```bash ```bash
# Ajouter tous les fichiers modifiés # Ajouter tous les fichiers modifiés
@@ -1447,8 +1501,15 @@ Hive Box:
Repositories: Repositories:
✅ UserRepository simplifié (plus d'injection ApiService) ✅ UserRepository simplifié (plus d'injection ApiService)
✅ AmicaleRepository simplifié ✅ AmicaleRepository simplifié
✅ MembreRepository corrigé selon vrais champs modèle
✅ ClientRepository corrigé selon vrais champs modèle
✅ Tous les repositories utilisent ApiService.instance ✅ Tous les repositories utilisent ApiService.instance
Models:
✅ MembreModel corrigé avec les vrais champs
✅ ClientModel complété avec champs manquants
✅ Méthodes create/update simplifiées dans repositories
UI/UX: UI/UX:
✅ Pages sans injections de dépendances ✅ Pages sans injections de dépendances
✅ Widgets UserInfoWidget et AmicaleInfoWidget réactifs ✅ Widgets UserInfoWidget et AmicaleInfoWidget réactifs
@@ -1468,6 +1529,7 @@ BREAKING CHANGES:
- Box Hive users renommée en user - Box Hive users renommée en user
- Constructeurs de pages/widgets simplifiés - Constructeurs de pages/widgets simplifiés
- Pattern d'accès aux données utilisateur/amicale changé - Pattern d'accès aux données utilisateur/amicale changé
- Champs de modèles corrigés selon spécifications
MIGRATION: Automatique au démarrage de l'app" MIGRATION: Automatique au démarrage de l'app"
@@ -1475,7 +1537,7 @@ MIGRATION: Automatique au démarrage de l'app"
git push origin singletons git push origin singletons
``` ```
#### Tâche 15.2: Documentation finale (15 min) #### Tâche 16.2: Documentation finale (15 min)
**Actions** : **Actions** :
@@ -1523,15 +1585,29 @@ final hasGps = CurrentAmicaleService.instance.hasGpsCoordinates;
final response = await ApiService.instance.get('/endpoint'); final response = await ApiService.instance.get('/endpoint');
``` ```
## Corrections modèles
### MembreModel
- Corrigé selon les vrais champs : `isActive`, `role`, etc.
- Plus de champs inventés
### ClientModel
- Ajout champs manquants : `chkStripe`, `createdAt`, `updatedAt`
## Migration ## Migration
- Box `users` renommée en `user` (migration automatique) - Box `users` renommée en `user` (migration automatique)
- Plus d'injection de dépendances dans les constructeurs - Plus d'injection de dépendances dans les constructeurs
- Widgets réactifs avec ListenableBuilder - Widgets réactifs avec ListenableBuilder
- Repositories simplifiés
``` ```
--- ---
## 📁 Structure finale des fichiers modifiés/créés ## 📁 Structure finale des fichiers modifiés/créés
### ✅ Fichiers déjà traités
``` ```

View File

@@ -23,7 +23,7 @@ GEOSECTOR est une application Flutter qui permet aux amicales de pompiers de gé
- **Framework** : Flutter 3.32 - **Framework** : Flutter 3.32
- **Routing** : Go Router - **Routing** : Go Router
- **Base de données locale** : Hive - **Base de données locale** : Hive
- **Gestion d'état** : Provider + Repository Pattern - **Gestion d'état** : Repository Pattern (Pas de Provider, Get.It, etc...)
- **Cartes** : Flutter Map avec Mapbox - **Cartes** : Flutter Map avec Mapbox
- **API** : Dio pour les requêtes HTTP - **API** : Dio pour les requêtes HTTP
- **Chat** : MQTT5 Client - **Chat** : MQTT5 Client
@@ -35,8 +35,6 @@ L'application utilise une architecture en couches pour la gestion des données :
``` ```
UI Layer (Widgets) UI Layer (Widgets)
Provider Layer (State Management)
Repository Layer (Business Logic) Repository Layer (Business Logic)
Data Layer (Hive + API) Data Layer (Hive + API)
@@ -45,9 +43,8 @@ Data Layer (Hive + API)
#### Couches de l'architecture #### Couches de l'architecture
1. **UI Layer** : Widgets Flutter qui affichent les données 1. **UI Layer** : Widgets Flutter qui affichent les données
2. **Provider Layer** : Gestion d'état avec ChangeNotifier 2. **Repository Layer** : Logique métier et orchestration des sources de données
3. **Repository Layer** : Logique métier et orchestration des sources de données 3. **Data Layer** : Stockage local (Hive) et API distante
4. **Data Layer** : Stockage local (Hive) et API distante
### Structure des dossiers ### Structure des dossiers
@@ -87,12 +84,14 @@ lib/
### Installation ### Installation
1. **Cloner le repository** 1. **Cloner le repository**
```bash ```bash
git clone https://github.com/votre-repo/geosector.git git clone https://github.com/votre-repo/geosector.git
cd geosector cd geosector
``` ```
2. **Installer les dépendances** 2. **Installer les dépendances**
```bash ```bash
flutter pub get flutter pub get
``` ```
@@ -100,6 +99,7 @@ lib/
3. **Configuration des clés API** 3. **Configuration des clés API**
Créer un fichier `.env` à la racine : Créer un fichier `.env` à la racine :
```env ```env
MAPBOX_ACCESS_TOKEN=your_mapbox_token MAPBOX_ACCESS_TOKEN=your_mapbox_token
API_BASE_URL=https://your-api.com API_BASE_URL=https://your-api.com
@@ -119,7 +119,6 @@ dependencies:
sdk: flutter sdk: flutter
# État et navigation # État et navigation
provider: ^6.1.1
go_router: ^12.1.3 go_router: ^12.1.3
# Stockage local # Stockage local
@@ -147,116 +146,53 @@ dev_dependencies:
## 🗄️ Modèles de données ## 🗄️ Modèles de données
### Secteur if (!Hive.isAdapterRegistered(0)) {
Hive.registerAdapter(UserModelAdapter());
```dart
@HiveType(typeId: 0)
class Secteur extends HiveObject {
@HiveField(0)
String id;
@HiveField(1)
String nom;
@HiveField(2)
List<LatLng> polygone;
@HiveField(3)
String couleur;
@HiveField(4)
int nombreCalendriers;
@HiveField(5)
DateTime dateCreation;
} }
``` if (!Hive.isAdapterRegistered(1)) {
Hive.registerAdapter(OperationModelAdapter());
### Passage
```dart
@HiveType(typeId: 1)
class Passage extends HiveObject {
@HiveField(0)
String id;
@HiveField(1)
String secteurId;
@HiveField(2)
String utilisateurId;
@HiveField(3)
DateTime datePassage;
@HiveField(4)
LatLng position;
@HiveField(5)
int calendriersDistribues;
@HiveField(6)
String commentaire;
@HiveField(7)
List<String> photos;
} }
``` if (!Hive.isAdapterRegistered(3)) {
Hive.registerAdapter(SectorModelAdapter());
### Utilisateur }
if (!Hive.isAdapterRegistered(4)) {
```dart Hive.registerAdapter(PassageModelAdapter());
@HiveType(typeId: 2) }
class Utilisateur extends HiveObject { if (!Hive.isAdapterRegistered(5)) {
@HiveField(0) Hive.registerAdapter(MembreModelAdapter());
String id; }
if (!Hive.isAdapterRegistered(6)) {
@HiveField(1) Hive.registerAdapter(UserSectorModelAdapter());
String nom; }
if (!Hive.isAdapterRegistered(7)) {
@HiveField(2) Hive.registerAdapter(RegionModelAdapter());
String email; }
if (!Hive.isAdapterRegistered(10)) {
@HiveField(3) Hive.registerAdapter(ClientModelAdapter());
String role; // 'admin' ou 'user' }
if (!Hive.isAdapterRegistered(11)) {
@HiveField(4) Hive.registerAdapter(AmicaleModelAdapter());
List<String> secteursAssignes;
@HiveField(5)
DateTime dernierLogin;
} }
```
## 🔧 Gestion d'état avec Provider // Chat adapters
if (!Hive.isAdapterRegistered(20)) {
### SecteurProvider Hive.registerAdapter(ConversationModelAdapter());
}
```dart if (!Hive.isAdapterRegistered(21)) {
class SecteurProvider extends ChangeNotifier { Hive.registerAdapter(MessageModelAdapter());
final SecteurRepository _repository; }
if (!Hive.isAdapterRegistered(22)) {
List<Secteur> _secteurs = []; Hive.registerAdapter(ParticipantModelAdapter());
bool _isLoading = false; }
if (!Hive.isAdapterRegistered(23)) {
List<Secteur> get secteurs => _secteurs; Hive.registerAdapter(AnonymousUserModelAdapter());
bool get isLoading => _isLoading; }
if (!Hive.isAdapterRegistered(24)) {
Future<void> loadSecteurs() async { Hive.registerAdapter(AudienceTargetModelAdapter());
_isLoading = true; }
notifyListeners(); if (!Hive.isAdapterRegistered(25)) {
Hive.registerAdapter(NotificationSettingsAdapter());
try {
_secteurs = await _repository.getAllSecteurs();
} catch (e) {
// Gestion d'erreur
} finally {
_isLoading = false;
notifyListeners();
} }
}
}
```
## 🗺️ Cartes et géolocalisation ## 🗺️ Cartes et géolocalisation

View File

@@ -1 +1 @@
{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/pierre/dev/geosector/app/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=9c247933552af22255bf791d596f2dce_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]} {"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/pierre/dev/geosector/app/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=0987d131841f12618fa22c05d7871702_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]}

View File

@@ -39,6 +39,6 @@ _flutter.buildConfig = {"engineRevision":"1425e5e9ec5eeb4f225c401d8db69b860e0fde
_flutter.loader.load({ _flutter.loader.load({
serviceWorkerSettings: { serviceWorkerSettings: {
serviceWorkerVersion: "3087561979" serviceWorkerVersion: "3209567515"
} }
}); });

View File

@@ -3,13 +3,13 @@ const MANIFEST = 'flutter-app-manifest';
const TEMP = 'flutter-temp-cache'; const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache'; const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {"flutter_bootstrap.js": "1dbbc20d0a6216228635de38eb5222a5", const RESOURCES = {"flutter_bootstrap.js": "dbeddb5a67a21614f83df7c02ea4f54e",
"version.json": "727f6f584c125faac83c6d2a4c96fb3d", "version.json": "727f6f584c125faac83c6d2a4c96fb3d",
"index.html": "2aab03d10fea3b608e3eddc0fc0077e5", "index.html": "2aab03d10fea3b608e3eddc0fc0077e5",
"/": "2aab03d10fea3b608e3eddc0fc0077e5", "/": "2aab03d10fea3b608e3eddc0fc0077e5",
"favicon-64.png": "259540a3217e969237530444ca0eaed3", "favicon-64.png": "259540a3217e969237530444ca0eaed3",
"favicon-16.png": "106142fb24eba190e475dbe6513cc9ff", "favicon-16.png": "106142fb24eba190e475dbe6513cc9ff",
"main.dart.js": "7530a148b4d52ca92f4706f660f16907", "main.dart.js": "50fc919ba8d82c79ce420d777c0230eb",
"flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c", "flutter.js": "83d881c1dbb6d6bcd6b42e274605b69c",
"favicon.png": "21510778ead066ac826ad69302400773", "favicon.png": "21510778ead066ac826ad69302400773",
"icons/Icon-192.png": "f36879dd176101fac324b68793e4683c", "icons/Icon-192.png": "f36879dd176101fac324b68793e4683c",

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@ part 'anonymous_user_model.g.dart';
/// Ce modèle représente un utilisateur anonyme (pour le cas Resalice) /// Ce modèle représente un utilisateur anonyme (pour le cas Resalice)
/// et permet de tracker sa conversion éventuelle en utilisateur authentifié /// et permet de tracker sa conversion éventuelle en utilisateur authentifié
@HiveType(typeId: 24) @HiveType(typeId: 23)
class AnonymousUserModel extends HiveObject with EquatableMixin { class AnonymousUserModel extends HiveObject with EquatableMixin {
@HiveField(0) @HiveField(0)
final String id; final String id;

View File

@@ -8,7 +8,7 @@ part of 'anonymous_user_model.dart';
class AnonymousUserModelAdapter extends TypeAdapter<AnonymousUserModel> { class AnonymousUserModelAdapter extends TypeAdapter<AnonymousUserModel> {
@override @override
final int typeId = 24; final int typeId = 23;
@override @override
AnonymousUserModel read(BinaryReader reader) { AnonymousUserModel read(BinaryReader reader) {

View File

@@ -8,7 +8,7 @@ part 'audience_target_model.g.dart';
/// Ce modèle représente une cible d'audience pour les annonces et broadcasts /// Ce modèle représente une cible d'audience pour les annonces et broadcasts
/// Il supporte maintenant le ciblage combiné avec les filtres de rôle et d'entité /// Il supporte maintenant le ciblage combiné avec les filtres de rôle et d'entité
@HiveType(typeId: 23) @HiveType(typeId: 24)
class AudienceTargetModel extends HiveObject with EquatableMixin { class AudienceTargetModel extends HiveObject with EquatableMixin {
@HiveField(0) @HiveField(0)
final String id; final String id;

View File

@@ -8,7 +8,7 @@ part of 'audience_target_model.dart';
class AudienceTargetModelAdapter extends TypeAdapter<AudienceTargetModel> { class AudienceTargetModelAdapter extends TypeAdapter<AudienceTargetModel> {
@override @override
final int typeId = 23; final int typeId = 24;
@override @override
AudienceTargetModel read(BinaryReader reader) { AudienceTargetModel read(BinaryReader reader) {

View File

@@ -61,6 +61,15 @@ class ClientModel extends HiveObject {
@HiveField(18) @HiveField(18)
final bool? chkActive; final bool? chkActive;
@HiveField(19)
final bool? chkStripe;
@HiveField(20)
final DateTime? createdAt;
@HiveField(21)
final DateTime? updatedAt;
ClientModel({ ClientModel({
required this.id, required this.id,
required this.name, required this.name,
@@ -81,6 +90,9 @@ class ClientModel extends HiveObject {
this.chkCopieMailRecu, this.chkCopieMailRecu,
this.chkAcceptSms, this.chkAcceptSms,
this.chkActive, this.chkActive,
this.chkStripe,
this.createdAt,
this.updatedAt,
}); });
// Factory pour convertir depuis JSON (API) // Factory pour convertir depuis JSON (API)
@@ -93,8 +105,7 @@ class ClientModel extends HiveObject {
int? fkRegion; int? fkRegion;
if (json['fk_region'] != null) { if (json['fk_region'] != null) {
final dynamic rawFkRegion = json['fk_region']; final dynamic rawFkRegion = json['fk_region'];
fkRegion = fkRegion = rawFkRegion is String ? int.parse(rawFkRegion) : rawFkRegion as int;
rawFkRegion is String ? int.parse(rawFkRegion) : rawFkRegion as int;
} }
// Convertir fk_type en int si présent // Convertir fk_type en int si présent
@@ -121,11 +132,12 @@ class ClientModel extends HiveObject {
gpsLng: json['gps_lng'], gpsLng: json['gps_lng'],
stripeId: json['stripe_id'], stripeId: json['stripe_id'],
chkDemo: json['chk_demo'] == 1 || json['chk_demo'] == true, chkDemo: json['chk_demo'] == 1 || json['chk_demo'] == true,
chkCopieMailRecu: json['chk_copie_mail_recu'] == 1 || chkCopieMailRecu: json['chk_copie_mail_recu'] == 1 || json['chk_copie_mail_recu'] == true,
json['chk_copie_mail_recu'] == true, chkAcceptSms: json['chk_accept_sms'] == 1 || json['chk_accept_sms'] == true,
chkAcceptSms:
json['chk_accept_sms'] == 1 || json['chk_accept_sms'] == true,
chkActive: json['chk_active'] == 1 || json['chk_active'] == true, chkActive: json['chk_active'] == 1 || json['chk_active'] == true,
chkStripe: json['chk_stripe'] == 1 || json['chk_stripe'] == true,
createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null,
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null,
); );
} }
@@ -151,6 +163,9 @@ class ClientModel extends HiveObject {
'chk_copie_mail_recu': chkCopieMailRecu, 'chk_copie_mail_recu': chkCopieMailRecu,
'chk_accept_sms': chkAcceptSms, 'chk_accept_sms': chkAcceptSms,
'chk_active': chkActive, 'chk_active': chkActive,
'chk_stripe': chkStripe,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
}; };
} }
@@ -174,9 +189,12 @@ class ClientModel extends HiveObject {
bool? chkCopieMailRecu, bool? chkCopieMailRecu,
bool? chkAcceptSms, bool? chkAcceptSms,
bool? chkActive, bool? chkActive,
bool? chkStripe,
DateTime? createdAt,
DateTime? updatedAt,
}) { }) {
return ClientModel( return ClientModel(
id: this.id, id: id,
name: name ?? this.name, name: name ?? this.name,
adresse1: adresse1 ?? this.adresse1, adresse1: adresse1 ?? this.adresse1,
adresse2: adresse2 ?? this.adresse2, adresse2: adresse2 ?? this.adresse2,
@@ -195,6 +213,9 @@ class ClientModel extends HiveObject {
chkCopieMailRecu: chkCopieMailRecu ?? this.chkCopieMailRecu, chkCopieMailRecu: chkCopieMailRecu ?? this.chkCopieMailRecu,
chkAcceptSms: chkAcceptSms ?? this.chkAcceptSms, chkAcceptSms: chkAcceptSms ?? this.chkAcceptSms,
chkActive: chkActive ?? this.chkActive, chkActive: chkActive ?? this.chkActive,
chkStripe: chkStripe ?? this.chkStripe,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
); );
} }
} }

View File

@@ -36,13 +36,16 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
chkCopieMailRecu: fields[16] as bool?, chkCopieMailRecu: fields[16] as bool?,
chkAcceptSms: fields[17] as bool?, chkAcceptSms: fields[17] as bool?,
chkActive: fields[18] as bool?, chkActive: fields[18] as bool?,
chkStripe: fields[19] as bool?,
createdAt: fields[20] as DateTime?,
updatedAt: fields[21] as DateTime?,
); );
} }
@override @override
void write(BinaryWriter writer, ClientModel obj) { void write(BinaryWriter writer, ClientModel obj) {
writer writer
..writeByte(19) ..writeByte(22)
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
@@ -80,7 +83,13 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
..writeByte(17) ..writeByte(17)
..write(obj.chkAcceptSms) ..write(obj.chkAcceptSms)
..writeByte(18) ..writeByte(18)
..write(obj.chkActive); ..write(obj.chkActive)
..writeByte(19)
..write(obj.chkStripe)
..writeByte(20)
..write(obj.createdAt)
..writeByte(21)
..write(obj.updatedAt);
} }
@override @override

View File

@@ -8,51 +8,53 @@ class MembreModel extends HiveObject {
final int id; final int id;
@HiveField(1) @HiveField(1)
final int fkRole; int? fkEntite;
@HiveField(2) @HiveField(2)
final int fkTitre; final int role;
@HiveField(3) @HiveField(3)
final String firstName; int? fkTitre;
@HiveField(4) @HiveField(4)
final String? sectName; String? name;
@HiveField(5) @HiveField(5)
final DateTime? dateNaissance; String? firstName;
@HiveField(6) @HiveField(6)
final DateTime? dateEmbauche; String? username;
@HiveField(7) @HiveField(7)
final int chkActive; String? sectName;
@HiveField(8) @HiveField(8)
final String name;
@HiveField(9)
final String username;
@HiveField(10)
final String email; final String email;
@HiveField(9)
String? phone;
@HiveField(10)
String? mobile;
@HiveField(11) @HiveField(11)
final int fkEntite; DateTime? dateNaissance;
@HiveField(12)
DateTime? dateEmbauche;
@HiveField(13)
final DateTime createdAt;
@HiveField(14)
bool isActive;
MembreModel({ MembreModel({
required this.id, required this.id,
required this.fkRole, this.fkEntite,
required this.fkTitre, required this.role,
required this.firstName, this.fkTitre,
this.name,
this.firstName,
this.username,
this.sectName, this.sectName,
required this.email,
this.phone,
this.mobile,
this.dateNaissance, this.dateNaissance,
this.dateEmbauche, this.dateEmbauche,
required this.chkActive, required this.createdAt,
required this.name, required this.isActive,
required this.username,
required this.email,
required this.fkEntite,
}); });
// Factory pour convertir depuis JSON (API) // Factory pour convertir depuis JSON (API)
@@ -61,35 +63,53 @@ class MembreModel extends HiveObject {
final dynamic rawId = json['id']; final dynamic rawId = json['id'];
final int id = rawId is String ? int.parse(rawId) : rawId as int; final int id = rawId is String ? int.parse(rawId) : rawId as int;
// Convertir le rôle en int, qu'il soit déjà int ou string // Convertir le rôle en int (ATTENTION: le champ JSON est 'fk_role' pas 'role')
final dynamic rawRole = json['fk_role']; final dynamic rawRole = json['fk_role']; // Correction ici !
final int fkRole = rawRole is String ? int.parse(rawRole) : rawRole as int; final int role = rawRole is String ? int.parse(rawRole) : rawRole as int;
// Convertir le titre en int, qu'il soit déjà int ou string // Convertir fkEntite en int si présent
final dynamic rawTitre = json['fk_titre']; int? fkEntite;
final int fkTitre = rawTitre is String ? int.parse(rawTitre) : rawTitre as int; if (json['fk_entite'] != null) {
final dynamic rawEntite = json['fk_entite'];
fkEntite = rawEntite is String ? int.parse(rawEntite) : rawEntite as int;
}
// Convertir le chkActive en int, qu'il soit déjà int ou string // Convertir fkTitre en int si présent
final dynamic rawActive = json['chk_active']; int? fkTitre;
final int chkActive = rawActive is String ? int.parse(rawActive) : rawActive as int; if (json['fk_titre'] != null) {
final dynamic rawTitre = json['fk_titre'];
fkTitre = rawTitre is String ? int.parse(rawTitre) : rawTitre as int;
}
// Convertir le fkEntite en int, qu'il soit déjà int ou string // Gérer les dates nulles ou avec des valeurs invalides comme "0000-00-00"
final dynamic rawEntite = json['fk_entite']; DateTime? parseDate(String? dateStr) {
final int fkEntite = rawEntite is String ? int.parse(rawEntite) : rawEntite as int; if (dateStr == null || dateStr.isEmpty || dateStr == "0000-00-00") {
return null;
}
try {
return DateTime.parse(dateStr);
} catch (e) {
return null;
}
}
return MembreModel( return MembreModel(
id: id, id: id,
fkRole: fkRole,
fkTitre: fkTitre,
firstName: json['first_name'] ?? '',
sectName: json['sect_name'],
dateNaissance: json['date_naissance'] != null ? DateTime.parse(json['date_naissance']) : null,
dateEmbauche: json['date_embauche'] != null ? DateTime.parse(json['date_embauche']) : null,
chkActive: chkActive,
name: json['name'] ?? '',
username: json['username'] ?? '',
email: json['email'] ?? '',
fkEntite: fkEntite, fkEntite: fkEntite,
role: role,
fkTitre: fkTitre,
name: json['name'],
firstName: json['first_name'],
username: json['username'],
sectName: json['sect_name'],
email: json['email'] ?? '',
phone: json['phone'],
mobile: json['mobile'],
dateNaissance: parseDate(json['date_naissance']),
dateEmbauche: parseDate(json['date_embauche']),
createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : DateTime.now(),
// Le champ JSON est 'chk_active' pas 'is_active'
isActive: json['chk_active'] == 1 || json['chk_active'] == true,
); );
} }
@@ -97,47 +117,56 @@ class MembreModel extends HiveObject {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'id': id, 'id': id,
'fk_role': fkRole, 'fk_entite': fkEntite,
'fk_role': role, // Changé pour correspondre à l'API
'fk_titre': fkTitre, 'fk_titre': fkTitre,
'name': name,
'first_name': firstName, 'first_name': firstName,
'username': username,
'sect_name': sectName, 'sect_name': sectName,
'email': email,
'phone': phone,
'mobile': mobile,
'date_naissance': dateNaissance?.toIso8601String(), 'date_naissance': dateNaissance?.toIso8601String(),
'date_embauche': dateEmbauche?.toIso8601String(), 'date_embauche': dateEmbauche?.toIso8601String(),
'chk_active': chkActive, 'created_at': createdAt.toIso8601String(),
'name': name, 'chk_active': isActive ? 1 : 0, // Changé pour correspondre à l'API
'username': username,
'email': email,
'fk_entite': fkEntite,
}; };
} }
// Copier avec de nouvelles valeurs // Copier avec de nouvelles valeurs
MembreModel copyWith({ MembreModel copyWith({
int? fkRole, int? fkEntite,
int? role,
int? fkTitre, int? fkTitre,
String? name,
String? firstName, String? firstName,
String? username,
String? sectName, String? sectName,
String? email,
String? phone,
String? mobile,
DateTime? dateNaissance, DateTime? dateNaissance,
DateTime? dateEmbauche, DateTime? dateEmbauche,
int? chkActive, DateTime? createdAt,
String? name, bool? isActive,
String? username,
String? email,
int? fkEntite,
}) { }) {
return MembreModel( return MembreModel(
id: id, id: id,
fkRole: fkRole ?? this.fkRole, fkEntite: fkEntite ?? this.fkEntite,
role: role ?? this.role,
fkTitre: fkTitre ?? this.fkTitre, fkTitre: fkTitre ?? this.fkTitre,
name: name ?? this.name,
firstName: firstName ?? this.firstName, firstName: firstName ?? this.firstName,
username: username ?? this.username,
sectName: sectName ?? this.sectName, sectName: sectName ?? this.sectName,
email: email ?? this.email,
phone: phone ?? this.phone,
mobile: mobile ?? this.mobile,
dateNaissance: dateNaissance ?? this.dateNaissance, dateNaissance: dateNaissance ?? this.dateNaissance,
dateEmbauche: dateEmbauche ?? this.dateEmbauche, dateEmbauche: dateEmbauche ?? this.dateEmbauche,
chkActive: chkActive ?? this.chkActive, createdAt: createdAt ?? this.createdAt,
name: name ?? this.name, isActive: isActive ?? this.isActive,
username: username ?? this.username,
email: email ?? this.email,
fkEntite: fkEntite ?? this.fkEntite,
); );
} }
} }

View File

@@ -18,48 +18,57 @@ class MembreModelAdapter extends TypeAdapter<MembreModel> {
}; };
return MembreModel( return MembreModel(
id: fields[0] as int, id: fields[0] as int,
fkRole: fields[1] as int, fkEntite: fields[1] as int?,
fkTitre: fields[2] as int, role: fields[2] as int,
firstName: fields[3] as String, fkTitre: fields[3] as int?,
sectName: fields[4] as String?, name: fields[4] as String?,
dateNaissance: fields[5] as DateTime?, firstName: fields[5] as String?,
dateEmbauche: fields[6] as DateTime?, username: fields[6] as String?,
chkActive: fields[7] as int, sectName: fields[7] as String?,
name: fields[8] as String, email: fields[8] as String,
username: fields[9] as String, phone: fields[9] as String?,
email: fields[10] as String, mobile: fields[10] as String?,
fkEntite: fields[11] as int, dateNaissance: fields[11] as DateTime?,
dateEmbauche: fields[12] as DateTime?,
createdAt: fields[13] as DateTime,
isActive: fields[14] as bool,
); );
} }
@override @override
void write(BinaryWriter writer, MembreModel obj) { void write(BinaryWriter writer, MembreModel obj) {
writer writer
..writeByte(12) ..writeByte(15)
..writeByte(0) ..writeByte(0)
..write(obj.id) ..write(obj.id)
..writeByte(1) ..writeByte(1)
..write(obj.fkRole) ..write(obj.fkEntite)
..writeByte(2) ..writeByte(2)
..write(obj.fkTitre) ..write(obj.role)
..writeByte(3) ..writeByte(3)
..write(obj.firstName) ..write(obj.fkTitre)
..writeByte(4) ..writeByte(4)
..write(obj.sectName)
..writeByte(5)
..write(obj.dateNaissance)
..writeByte(6)
..write(obj.dateEmbauche)
..writeByte(7)
..write(obj.chkActive)
..writeByte(8)
..write(obj.name) ..write(obj.name)
..writeByte(9) ..writeByte(5)
..write(obj.firstName)
..writeByte(6)
..write(obj.username) ..write(obj.username)
..writeByte(10) ..writeByte(7)
..write(obj.sectName)
..writeByte(8)
..write(obj.email) ..write(obj.email)
..writeByte(9)
..write(obj.phone)
..writeByte(10)
..write(obj.mobile)
..writeByte(11) ..writeByte(11)
..write(obj.fkEntite); ..write(obj.dateNaissance)
..writeByte(12)
..write(obj.dateEmbauche)
..writeByte(13)
..write(obj.createdAt)
..writeByte(14)
..write(obj.isActive);
} }
@override @override

View File

@@ -2,7 +2,7 @@ import 'package:hive/hive.dart';
part 'region_model.g.dart'; part 'region_model.g.dart';
@HiveType(typeId: 7) // Assurez-vous que cet ID est unique @HiveType(typeId: 7)
class RegionModel extends HiveObject { class RegionModel extends HiveObject {
@HiveField(0) @HiveField(0)
final int id; final int id;

View File

@@ -6,8 +6,7 @@ part 'user_sector_model.g.dart';
/// ///
/// Cette classe représente l'association entre un utilisateur et un secteur, /// Cette classe représente l'association entre un utilisateur et un secteur,
/// telle que reçue de l'API dans la réponse users_sectors. /// telle que reçue de l'API dans la réponse users_sectors.
@HiveType( @HiveType(typeId: 6)
typeId: 7) // Assurez-vous que cet ID est unique parmi vos modèles Hive
class UserSectorModel extends HiveObject { class UserSectorModel extends HiveObject {
@HiveField(0) @HiveField(0)
final int id; // ID de l'utilisateur final int id; // ID de l'utilisateur
@@ -38,9 +37,7 @@ class UserSectorModel extends HiveObject {
id: json['id'] is String ? int.parse(json['id']) : json['id'], id: json['id'] is String ? int.parse(json['id']) : json['id'],
firstName: json['first_name'], firstName: json['first_name'],
sectName: json['sect_name'], sectName: json['sect_name'],
fkSector: json['fk_sector'] is String fkSector: json['fk_sector'] is String ? int.parse(json['fk_sector']) : json['fk_sector'],
? int.parse(json['fk_sector'])
: json['fk_sector'],
name: json['name'], name: json['name'],
); );
} }

View File

@@ -8,7 +8,7 @@ part of 'user_sector_model.dart';
class UserSectorModelAdapter extends TypeAdapter<UserSectorModel> { class UserSectorModelAdapter extends TypeAdapter<UserSectorModel> {
@override @override
final int typeId = 7; final int typeId = 6;
@override @override
UserSectorModel read(BinaryReader reader) { UserSectorModel read(BinaryReader reader) {

View File

@@ -1,8 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.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/services/api_service.dart'; import 'package:geosector_app/core/services/api_service.dart';
@@ -34,6 +31,19 @@ class AmicaleRepository extends ChangeNotifier {
} }
} }
// 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;
}
}
// Récupérer toutes les amicales // Récupérer toutes les amicales
List<AmicaleModel> getAllAmicales() { List<AmicaleModel> getAllAmicales() {
return _amicaleBox.values.toList(); return _amicaleBox.values.toList();
@@ -54,6 +64,16 @@ class AmicaleRepository extends ChangeNotifier {
return getAmicalesByType(1); return getAmicalesByType(1);
} }
// Récupérer l'amicale de l'utilisateur connecté (basé sur fkEntite)
AmicaleModel? getAmicaleByUserId(int userId, int fkEntite) {
try {
return _amicaleBox.get(fkEntite);
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale de l\'utilisateur: $e');
return null;
}
}
// Sauvegarder une amicale // Sauvegarder une amicale
Future<void> saveAmicale(AmicaleModel amicale) async { Future<void> saveAmicale(AmicaleModel amicale) async {
await _amicaleBox.put(amicale.id, amicale); await _amicaleBox.put(amicale.id, amicale);
@@ -66,6 +86,12 @@ class AmicaleRepository extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
// Vider la boîte des amicales
Future<void> clearAmicales() async {
await _amicaleBox.clear();
notifyListeners();
}
// Créer une amicale via l'API // Créer une amicale via l'API
Future<bool> createAmicale(AmicaleModel amicale) async { Future<bool> createAmicale(AmicaleModel amicale) async {
_isLoading = true; _isLoading = true;
@@ -82,14 +108,33 @@ class AmicaleRepository extends ChangeNotifier {
// Récupérer l'ID de la nouvelle amicale // Récupérer l'ID de la nouvelle amicale
final amicaleId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int; final amicaleId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
// Créer l'amicale localement avec l'ID retourné par l'API // Créer l'amicale localement avec l'ID retourné par l'API et updatedAt
final newAmicale = amicale.copyWith( final amicaleWithNewId = AmicaleModel(
id: amicaleId, id: amicaleId,
lastSyncedAt: DateTime.now(), name: amicale.name,
isSynced: true, adresse1: amicale.adresse1,
adresse2: amicale.adresse2,
codePostal: amicale.codePostal,
ville: amicale.ville,
fkRegion: amicale.fkRegion,
libRegion: amicale.libRegion,
fkType: amicale.fkType,
phone: amicale.phone,
mobile: amicale.mobile,
email: amicale.email,
gpsLat: amicale.gpsLat,
gpsLng: amicale.gpsLng,
stripeId: amicale.stripeId,
chkDemo: amicale.chkDemo,
chkCopieMailRecu: amicale.chkCopieMailRecu,
chkAcceptSms: amicale.chkAcceptSms,
chkActive: amicale.chkActive,
chkStripe: amicale.chkStripe,
createdAt: amicale.createdAt ?? DateTime.now(),
updatedAt: DateTime.now(),
); );
await saveAmicale(newAmicale); await saveAmicale(amicaleWithNewId);
return true; return true;
} }
@@ -116,10 +161,9 @@ class AmicaleRepository extends ChangeNotifier {
final response = await ApiService.instance.put('/amicales/${amicale.id}', data: data); final response = await ApiService.instance.put('/amicales/${amicale.id}', data: data);
if (response.statusCode == 200) { if (response.statusCode == 200) {
// Mettre à jour l'amicale localement // Mettre à jour l'amicale localement avec updatedAt
final updatedAmicale = amicale.copyWith( final updatedAmicale = amicale.copyWith(
lastSyncedAt: DateTime.now(), updatedAt: DateTime.now(),
isSynced: true,
); );
await saveAmicale(updatedAmicale); await saveAmicale(updatedAmicale);
@@ -136,6 +180,35 @@ class AmicaleRepository extends ChangeNotifier {
} }
} }
// Mettre à jour une amicale via l'API (version alternative avec retour d'objet)
Future<AmicaleModel?> updateAmicaleViaApi(AmicaleModel amicale) async {
_isLoading = true;
notifyListeners();
try {
final response = await ApiService.instance.put(
'/amicales/${amicale.id}',
data: amicale.toJson(),
);
if (response.statusCode == 200) {
final updatedAmicaleData = response.data;
final updatedAmicale = AmicaleModel.fromJson(updatedAmicaleData);
await saveAmicale(updatedAmicale);
return updatedAmicale;
} else {
debugPrint('Erreur lors de la mise à jour de l\'amicale: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la mise à jour de l\'amicale: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Supprimer une amicale via l'API // Supprimer une amicale via l'API
Future<bool> deleteAmicaleViaApi(int id) async { Future<bool> deleteAmicaleViaApi(int id) async {
_isLoading = true; _isLoading = true;
@@ -161,228 +234,55 @@ class AmicaleRepository extends ChangeNotifier {
} }
} }
// Traitement des données d'amicales depuis l'API // Traitement des données d'amicale depuis l'API (amicale unique)
Future<void> processAmicalesData(dynamic amicalesData) async { Future<void> processAmicalesData(dynamic amicaleData) async {
try { try {
debugPrint('Traitement des données des amicales...'); debugPrint('Traitement de l\'amicale utilisateur...');
// Vérifier que les données sont au bon format // Vérifier que les données sont au bon format
if (amicalesData == null) { if (amicaleData == null) {
debugPrint('Aucune donnée d\'amicale à traiter'); debugPrint('Aucune donnée d\'amicale à traiter');
return; return;
} }
List<dynamic> amicalesList; // Vider la boîte avant d'ajouter la nouvelle amicale
if (amicalesData is List) {
amicalesList = amicalesData;
} else if (amicalesData is Map && amicalesData.containsKey('data')) {
amicalesList = amicalesData['data'] as List<dynamic>;
} else {
debugPrint('Format de données d\'amicales non reconnu');
return;
}
// Vider la boîte avant d'ajouter les nouvelles données
await _amicaleBox.clear(); await _amicaleBox.clear();
// Traiter chaque amicale try {
int count = 0; // Les données sont un objet amicale unique
for (final amicaleData in amicalesList) { final Map<String, dynamic> amicaleMap = Map<String, dynamic>.from(amicaleData as Map);
try { final amicale = AmicaleModel.fromJson(amicaleMap);
final amicale = AmicaleModel.fromJson(amicaleData); await _amicaleBox.put(amicale.id, amicale);
await _amicaleBox.put(amicale.id, amicale); debugPrint('✅ Amicale utilisateur traitée: ${amicale.name} (ID: ${amicale.id})');
count++; notifyListeners();
} catch (e) { } catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e'); debugPrint('Erreur lors du traitement de l\'amicale: $e');
} debugPrint('Données reçues: $amicaleData');
}
debugPrint('$count amicales traitées et stockées');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement des amicales: $e');
}
}
}
import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
class AmicaleRepository extends ChangeNotifier {
// Utilisation de getters lazy pour n'accéder à la boîte que lorsque nécessaire
Box<AmicaleModel> get _amicaleBox => Hive.box<AmicaleModel>(AppKeys.amicaleBoxName);
final ApiService _apiService;
bool _isLoading = false;
AmicaleRepository(this._apiService);
// Getters
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
Future<void> _ensureBoxIsOpen() async {
try {
if (!Hive.isBoxOpen(AppKeys.amicaleBoxName)) {
debugPrint('Ouverture de la boîte amicale...');
await Hive.openBox<AmicaleModel>(AppKeys.amicaleBoxName);
} }
} catch (e) { } catch (e) {
debugPrint('Erreur lors de l\'ouverture de la boîte amicale: $e'); debugPrint('Erreur lors du traitement de l\'amicale: $e');
throw Exception('Impossible d\'ouvrir la boîte amicale: $e');
} }
} }
// Récupérer toutes les amicales // Méthode spécifique pour récupérer l'amicale de l'utilisateur connecté
List<AmicaleModel> getAllAmicales() { AmicaleModel? getUserAmicale(int fkEntite) {
try { try {
_ensureBoxIsOpen(); final amicale = _amicaleBox.get(fkEntite);
return _amicaleBox.values.toList(); debugPrint('🔍 Recherche amicale ID $fkEntite: ${amicale?.name ?? 'non trouvée'}');
return amicale;
} catch (e) { } catch (e) {
debugPrint('Erreur lors de la récupération des amicales: $e'); debugPrint('Erreur lors de la récupération de l\'amicale utilisateur: $e');
return [];
}
}
// Récupérer une amicale par son ID
AmicaleModel? getAmicaleById(int id) {
try {
_ensureBoxIsOpen();
return _amicaleBox.get(id);
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale: $e');
return null; return null;
} }
} }
// Récupérer l'amicale de l'utilisateur connecté (basé sur fkEntite)
AmicaleModel? getAmicaleByUserId(int userId, int fkEntite) {
try {
_ensureBoxIsOpen();
return _amicaleBox.get(fkEntite);
} catch (e) {
debugPrint('Erreur lors de la récupération de l\'amicale de l\'utilisateur: $e');
return null;
}
}
// Créer ou mettre à jour une amicale localement
Future<AmicaleModel> saveAmicale(AmicaleModel amicale) async {
await _ensureBoxIsOpen();
await _amicaleBox.put(amicale.id, amicale);
notifyListeners(); // Notifier les changements pour mettre à jour l'UI
return amicale;
}
// Supprimer une amicale localement
Future<void> deleteAmicale(int id) async {
await _ensureBoxIsOpen();
await _amicaleBox.delete(id);
notifyListeners();
}
// Vider la boîte des amicales
Future<void> clearAmicales() async {
await _ensureBoxIsOpen();
await _amicaleBox.clear();
notifyListeners();
}
// Traiter les données des amicales reçues de l'API
Future<void> processAmicalesData(dynamic amicalesData) async {
try {
debugPrint('Traitement des données des amicales...');
debugPrint('Détails amicale: $amicalesData');
// Vérifier que les données sont au bon format
if (amicalesData == null) {
debugPrint('Aucune donnée d\'amicale à traiter');
return;
}
// Vider la boîte avant d'ajouter les nouvelles données
await _ensureBoxIsOpen();
await _amicaleBox.clear();
int count = 0;
// Cas 1: Les données sont une liste d'amicales
if (amicalesData is List) {
for (final amicaleData in amicalesData) {
try {
final amicale = AmicaleModel.fromJson(amicaleData);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e');
}
}
}
// Cas 2: Les données sont un objet avec une clé 'data' contenant une liste
else if (amicalesData is Map && amicalesData.containsKey('data')) {
final amicalesList = amicalesData['data'] as List<dynamic>;
for (final amicaleData in amicalesList) {
try {
final amicale = AmicaleModel.fromJson(amicaleData);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement d\'une amicale: $e');
}
}
}
// Cas 3: Les données sont un objet amicale unique (pas une liste)
else if (amicalesData is Map) {
try {
// Convertir Map<dynamic, dynamic> en Map<String, dynamic>
final Map<String, dynamic> amicaleMap = {};
amicalesData.forEach((key, value) {
if (key is String) {
amicaleMap[key] = value;
}
});
final amicale = AmicaleModel.fromJson(amicaleMap);
await _amicaleBox.put(amicale.id, amicale);
count++;
debugPrint('Amicale unique traitée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('Erreur lors du traitement de l\'amicale unique: $e');
debugPrint('Exception détaillée: $e');
}
} else {
debugPrint('Format de données d\'amicale non reconnu');
return;
}
debugPrint('$count amicales traitées et stockées');
notifyListeners();
} catch (e) {
debugPrint('Erreur lors du traitement des amicales: $e');
}
}
// Récupérer les amicales depuis l'API // Récupérer les amicales depuis l'API
Future<List<AmicaleModel>> fetchAmicalesFromApi() async { Future<List<AmicaleModel>> fetchAmicalesFromApi() async {
_isLoading = true; _isLoading = true;
notifyListeners(); notifyListeners();
try { try {
final response = await _apiService.get('/amicales'); final response = await ApiService.instance.get('/amicales');
if (response.statusCode == 200) { if (response.statusCode == 200) {
final amicalesData = response.data; final amicalesData = response.data;
@@ -407,7 +307,7 @@ class AmicaleRepository extends ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
final response = await _apiService.get('/amicales/$id'); final response = await ApiService.instance.get('/amicales/$id');
if (response.statusCode == 200) { if (response.statusCode == 200) {
final amicaleData = response.data; final amicaleData = response.data;
@@ -427,34 +327,7 @@ class AmicaleRepository extends ChangeNotifier {
} }
} }
// Mettre à jour une amicale via l'API // === MÉTHODES DE FILTRAGE ET RECHERCHE ===
Future<AmicaleModel?> updateAmicaleViaApi(AmicaleModel amicale) async {
_isLoading = true;
notifyListeners();
try {
final response = await _apiService.put(
'/amicales/${amicale.id}',
data: amicale.toJson(),
);
if (response.statusCode == 200) {
final updatedAmicaleData = response.data;
final updatedAmicale = AmicaleModel.fromJson(updatedAmicaleData);
await saveAmicale(updatedAmicale);
return updatedAmicale;
} else {
debugPrint('Erreur lors de la mise à jour de l\'amicale: ${response.statusCode}');
return null;
}
} catch (e) {
debugPrint('Erreur lors de la mise à jour de l\'amicale: $e');
return null;
} finally {
_isLoading = false;
notifyListeners();
}
}
// Filtrer les amicales par nom // Filtrer les amicales par nom
List<AmicaleModel> searchAmicalesByName(String query) { List<AmicaleModel> searchAmicalesByName(String query) {
@@ -466,11 +339,6 @@ class AmicaleRepository extends ChangeNotifier {
return _amicaleBox.values.where((amicale) => amicale.name.toLowerCase().contains(lowercaseQuery)).toList(); return _amicaleBox.values.where((amicale) => amicale.name.toLowerCase().contains(lowercaseQuery)).toList();
} }
// Filtrer les amicales par type
List<AmicaleModel> getAmicalesByType(int type) {
return _amicaleBox.values.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.where((amicale) => amicale.fkRegion == regionId).toList(); return _amicaleBox.values.where((amicale) => amicale.fkRegion == regionId).toList();

View File

@@ -8,6 +8,7 @@ import 'package:geosector_app/core/constants/app_keys.dart';
class ClientRepository extends ChangeNotifier { class ClientRepository extends ChangeNotifier {
// Constructeur sans paramètres - utilise ApiService.instance // Constructeur sans paramètres - utilise ApiService.instance
ClientRepository(); ClientRepository();
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire // Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
// et vérifier qu'elle est ouverte avant accès // et vérifier qu'elle est ouverte avant accès
Box<ClientModel> get _clientBox { Box<ClientModel> get _clientBox {
@@ -58,8 +59,11 @@ class ClientRepository extends ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
// Préparer les données pour l'API // Préparer les données pour l'API - exclure l'id pour la création
final data = client.toJson(); final data = client.toJson();
data.remove('id'); // L'API génère l'ID
data.remove('created_at'); // L'API génère created_at
data.remove('updated_at'); // L'API génère updated_at
// Appeler l'API pour créer le client // Appeler l'API pour créer le client
final response = await ApiService.instance.post('/clients', data: data); final response = await ApiService.instance.post('/clients', data: data);
@@ -70,11 +74,13 @@ class ClientRepository extends ChangeNotifier {
// Créer le client localement avec l'ID retourné par l'API // Créer le client localement avec l'ID retourné par l'API
final newClient = client.copyWith( final newClient = client.copyWith(
id: clientId, createdAt: DateTime.now(),
lastSyncedAt: DateTime.now(), updatedAt: DateTime.now(),
isSynced: true,
); );
await saveClient(newClient);
// Sauvegarder avec le nouvel ID
await _clientBox.put(clientId, newClient);
notifyListeners();
return true; return true;
} }
return false; return false;
@@ -95,17 +101,14 @@ class ClientRepository extends ChangeNotifier {
try { try {
// Préparer les données pour l'API // Préparer les données pour l'API
final data = client.toJson(); final data = client.toJson();
// Appeler l'API pour mettre à jour le client // Appeler l'API pour mettre à jour le client
final response = await ApiService.instance.put('/clients/${client.id}', data: data); final response = await ApiService.instance.put('/clients/${client.id}', data: data);
if (response.statusCode == 200) { if (response.statusCode == 200) {
// Mettre à jour le client localement // Mettre à jour le client localement avec updatedAt
final updatedClient = client.copyWith( final updatedClient = client.copyWith(
lastSyncedAt: DateTime.now(), updatedAt: DateTime.now(),
isSynced: true,
); );
await saveClient(updatedClient); await saveClient(updatedClient);
return true; return true;
} }
@@ -190,7 +193,6 @@ class ClientRepository extends ChangeNotifier {
// Vider la boîte des clients // Vider la boîte des clients
Future<void> clearClients() async { Future<void> clearClients() async {
await _ensureBoxIsOpen();
await _clientBox.clear(); await _clientBox.clear();
notifyListeners(); notifyListeners();
} }
@@ -220,6 +222,7 @@ class ClientRepository extends ChangeNotifier {
} }
} }
// === MÉTHODES DE FILTRAGE ET RECHERCHE ===
// Filtrer les clients par nom // Filtrer les clients par nom
List<ClientModel> searchClientsByName(String query) { List<ClientModel> searchClientsByName(String query) {
if (query.isEmpty) { if (query.isEmpty) {

View File

@@ -8,6 +8,7 @@ import 'package:geosector_app/core/constants/app_keys.dart';
class MembreRepository extends ChangeNotifier { class MembreRepository extends ChangeNotifier {
// Constructeur sans paramètres - utilise ApiService.instance // Constructeur sans paramètres - utilise ApiService.instance
MembreRepository(); MembreRepository();
// Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire // Utiliser un getter lazy pour n'accéder à la boîte que lorsque nécessaire
// et vérifier qu'elle est ouverte avant accès // et vérifier qu'elle est ouverte avant accès
Box<MembreModel> get _membreBox { Box<MembreModel> get _membreBox {
@@ -43,6 +44,9 @@ class MembreRepository extends ChangeNotifier {
} }
} }
// === MÉTHODES SPÉCIFIQUES AUX MEMBRES ===
// Récupérer les membres par amicale
List<MembreModel> getMembresByAmicale(int fkEntite) { List<MembreModel> getMembresByAmicale(int fkEntite) {
try { try {
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite).toList(); return _membreBox.values.where((membre) => membre.fkEntite == fkEntite).toList();
@@ -55,7 +59,7 @@ class MembreRepository extends ChangeNotifier {
// Récupérer les membres actifs par amicale // Récupérer les membres actifs par amicale
List<MembreModel> getActiveMembresByAmicale(int fkEntite) { List<MembreModel> getActiveMembresByAmicale(int fkEntite) {
try { try {
return _membreBox.values.where((membre) => membre.fkEntite == fkEntite && membre.chkActive == 1).toList(); return _membreBox.values.where((membre) => membre.fkEntite == fkEntite && membre.isActive == true).toList();
} catch (e) { } catch (e) {
debugPrint('Erreur lors de la récupération des membres actifs par amicale: $e'); debugPrint('Erreur lors de la récupération des membres actifs par amicale: $e');
return []; return [];
@@ -72,6 +76,8 @@ class MembreRepository extends ChangeNotifier {
} }
} }
// === MÉTHODES CRUD DE BASE ===
// Récupérer tous les membres // Récupérer tous les membres
List<MembreModel> getAllMembres() { List<MembreModel> getAllMembres() {
try { try {
@@ -94,27 +100,28 @@ class MembreRepository extends ChangeNotifier {
// Sauvegarder un membre // Sauvegarder un membre
Future<void> saveMembre(MembreModel membre) async { Future<void> saveMembre(MembreModel membre) async {
await _ensureBoxIsOpen();
await _membreBox.put(membre.id, membre); await _membreBox.put(membre.id, membre);
notifyListeners(); notifyListeners();
} }
// Supprimer un membre // Supprimer un membre
Future<void> deleteMembre(int id) async { Future<void> deleteMembre(int id) async {
await _ensureBoxIsOpen();
await _membreBox.delete(id); await _membreBox.delete(id);
notifyListeners(); notifyListeners();
} }
// === MÉTHODES API ===
// Créer un membre via l'API // Créer un membre via l'API
Future<bool> createMembre(MembreModel membre) async { Future<bool> createMembre(MembreModel membre) async {
_isLoading = true; _isLoading = true;
notifyListeners(); notifyListeners();
try { try {
// Préparer les données pour l'API // Préparer les données pour l'API - exclure l'id pour la création
final data = membre.toJson(); final data = membre.toJson();
data.remove('id'); // L'API génère l'ID
data.remove('created_at'); // L'API génère created_at
// Appeler l'API pour créer le membre // Appeler l'API pour créer le membre
final response = await ApiService.instance.post('/membres', data: data); final response = await ApiService.instance.post('/membres', data: data);
@@ -123,11 +130,24 @@ class MembreRepository extends ChangeNotifier {
final membreId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int; final membreId = response.data['id'] is String ? int.parse(response.data['id']) : response.data['id'] as int;
// Créer le membre localement avec l'ID retourné par l'API // Créer le membre localement avec l'ID retourné par l'API
final newMembre = membre.copyWith( final newMembre = MembreModel(
id: membreId, id: membreId,
lastSyncedAt: DateTime.now(), fkEntite: membre.fkEntite,
isSynced: true, role: membre.role,
fkTitre: membre.fkTitre,
name: membre.name,
firstName: membre.firstName,
username: membre.username,
sectName: membre.sectName,
email: membre.email,
phone: membre.phone,
mobile: membre.mobile,
dateNaissance: membre.dateNaissance,
dateEmbauche: membre.dateEmbauche,
createdAt: DateTime.now(),
isActive: membre.isActive,
); );
await saveMembre(newMembre); await saveMembre(newMembre);
return true; return true;
} }
@@ -154,13 +174,8 @@ class MembreRepository extends ChangeNotifier {
final response = await ApiService.instance.put('/membres/${membre.id}', data: data); final response = await ApiService.instance.put('/membres/${membre.id}', data: data);
if (response.statusCode == 200) { if (response.statusCode == 200) {
// Mettre à jour le membre localement // Sauvegarder le membre mis à jour localement
final updatedMembre = membre.copyWith( await saveMembre(membre);
lastSyncedAt: DateTime.now(),
isSynced: true,
);
await saveMembre(updatedMembre);
return true; return true;
} }
@@ -199,6 +214,8 @@ class MembreRepository extends ChangeNotifier {
} }
} }
// === TRAITEMENT DES DONNÉES ===
// Traitement des données de membres depuis l'API // Traitement des données de membres depuis l'API
Future<void> processMembresData(dynamic membresData) async { Future<void> processMembresData(dynamic membresData) async {
try { try {
@@ -221,9 +238,7 @@ class MembreRepository extends ChangeNotifier {
} }
// Vider la boîte avant d'ajouter les nouvelles données // Vider la boîte avant d'ajouter les nouvelles données
await _ensureBoxIsOpen();
await _membreBox.clear(); await _membreBox.clear();
// Traiter chaque membre // Traiter chaque membre
int count = 0; int count = 0;
for (final membreData in membresList) { for (final membreData in membresList) {
@@ -242,4 +257,55 @@ class MembreRepository extends ChangeNotifier {
debugPrint('Erreur lors du traitement des membres: $e'); debugPrint('Erreur lors du traitement des membres: $e');
} }
} }
// Récupérer les membres depuis l'API
Future<List<MembreModel>> fetchMembresFromApi() async {
_isLoading = true;
notifyListeners();
try {
final response = await ApiService.instance.get('/membres');
if (response.statusCode == 200) {
final membresData = response.data;
await processMembresData(membresData);
return getAllMembres();
} else {
debugPrint('Erreur lors de la récupération des membres: ${response.statusCode}');
return [];
}
} catch (e) {
debugPrint('Erreur lors de la récupération des membres: $e');
return [];
} finally {
_isLoading = false;
notifyListeners();
}
}
// === MÉTHODES DE FILTRAGE ET RECHERCHE ===
// Filtrer les membres par nom
List<MembreModel> searchMembresByName(String query) {
if (query.isEmpty) {
return getAllMembres();
}
final lowercaseQuery = query.toLowerCase();
return _membreBox.values
.where(
(membre) => (membre.name?.toLowerCase().contains(lowercaseQuery) ?? false) || (membre.firstName?.toLowerCase().contains(lowercaseQuery) ?? false))
.toList();
}
// Filtrer les membres actifs
List<MembreModel> getActiveMembres() {
return _membreBox.values.where((membre) => membre.isActive == true).toList();
}
// Vider la boîte des membres
Future<void> clearMembres() async {
await _membreBox.clear();
notifyListeners();
}
} }

View File

@@ -68,6 +68,11 @@ class PassageRepository extends ChangeNotifier {
return _passageBox.values.where((passage) => passage.fkType == type).toList(); return _passageBox.values.where((passage) => passage.fkType == type).toList();
} }
// Récupérer les passages par opération
List<PassageModel> getPassagesByOperation(int operationId) {
return _passageBox.values.where((passage) => passage.fkOperation == operationId).toList();
}
// Récupérer les passages par date // Récupérer les passages par date
List<PassageModel> getPassagesByDate(DateTime date) { List<PassageModel> getPassagesByDate(DateTime date) {
return _passageBox.values.where((passage) { return _passageBox.values.where((passage) {

View File

@@ -226,9 +226,6 @@ class UserRepository extends ChangeNotifier {
try { try {
debugPrint('🔐 Tentative de connexion: $username'); debugPrint('🔐 Tentative de connexion: $username');
// Étape 1: Nettoyage des données via DataLoadingService
await DataLoadingService.instance.cleanDataBeforeLogin();
// Étape 2: Connexion à l'API (25%) // Étape 2: Connexion à l'API (25%)
final apiResult = await loginAPI(username, password, type: type); final apiResult = await loginAPI(username, password, type: type);
@@ -264,7 +261,13 @@ class UserRepository extends ChangeNotifier {
} }
// Étape 5: Traitement de toutes les autres données via DataLoadingService // Étape 5: Traitement de toutes les autres données via DataLoadingService
await DataLoadingService.instance.processLoginData(apiResult); try {
await DataLoadingService.instance.processLoginData(apiResult);
} catch (processingError) {
debugPrint('❌ Erreur lors du traitement des données: $processingError');
// On continue quand même car l'utilisateur est connecté
debugPrint('⚠️ Connexion réussie mais avec des données partielles');
}
debugPrint('✅ Connexion réussie'); debugPrint('✅ Connexion réussie');
return true; return true;

View File

@@ -72,8 +72,6 @@ class ApiService {
return computation(); return computation();
} }
// === TOUTES LES MÉTHODES EXISTANTES RESTENT IDENTIQUES ===
// Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL // Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL
String _determineEnvironment() { String _determineEnvironment() {
if (!kIsWeb) { if (!kIsWeb) {
@@ -136,7 +134,7 @@ class ApiService {
// Vérifier la connectivité réseau // Vérifier la connectivité réseau
Future<bool> hasInternetConnection() async { Future<bool> hasInternetConnection() async {
final connectivityResult = await (Connectivity().checkConnectivity()); final connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult != ConnectivityResult.none; return connectivityResult.contains(ConnectivityResult.none) == false;
} }
// Méthode POST générique // Méthode POST générique
@@ -273,10 +271,6 @@ class ApiService {
} }
} }
// Espace réservé pour les futures méthodes de gestion des profils
// Espace réservé pour les futures méthodes de gestion des données
// Synchronisation en batch // Synchronisation en batch
Future<Map<String, dynamic>> syncData({ Future<Map<String, dynamic>> syncData({
List<UserModel>? users, List<UserModel>? users,
@@ -297,228 +291,4 @@ class ApiService {
static void reset() { static void reset() {
_instance = null; _instance = null;
} }
}
final Dio _dio = Dio();
late final String _baseUrl;
late final String _appIdentifier;
String? _sessionId;
// Détermine l'environnement actuel (DEV, REC, PROD) en fonction de l'URL
String _determineEnvironment() {
if (!kIsWeb) {
// En mode non-web, utiliser l'environnement de développement par défaut
return 'DEV';
}
final currentUrl = html.window.location.href.toLowerCase();
if (currentUrl.contains('dapp.geosector.fr')) {
return 'DEV';
} else if (currentUrl.contains('rapp.geosector.fr')) {
return 'REC';
} else {
return 'PROD';
}
}
// Configure l'URL de base API et l'identifiant d'application selon l'environnement
void _configureEnvironment() {
final env = _determineEnvironment();
switch (env) {
case 'DEV':
_baseUrl = AppKeys.baseApiUrlDev;
_appIdentifier = AppKeys.appIdentifierDev;
break;
case 'REC':
_baseUrl = AppKeys.baseApiUrlRec;
_appIdentifier = AppKeys.appIdentifierRec;
break;
default: // PROD
_baseUrl = AppKeys.baseApiUrlProd;
_appIdentifier = AppKeys.appIdentifierProd;
}
debugPrint('GEOSECTOR 🔗 Environnement: $env, API: $_baseUrl');
}
// Définir l'ID de session
void setSessionId(String? sessionId) {
_sessionId = sessionId;
}
// Obtenir l'environnement actuel (utile pour le débogage)
String getCurrentEnvironment() {
return _determineEnvironment();
}
// Obtenir l'URL API actuelle (utile pour le débogage)
String getCurrentApiUrl() {
return _baseUrl;
}
// Obtenir l'identifiant d'application actuel (utile pour le débogage)
String getCurrentAppIdentifier() {
return _appIdentifier;
}
// Vérifier la connectivité réseau
Future<bool> hasInternetConnection() async {
final connectivityResult = await (Connectivity().checkConnectivity());
return connectivityResult.contains(ConnectivityResult.none) == false;
}
// Méthode POST générique
Future<Response> post(String path, {dynamic data}) async {
try {
return await _dio.post(path, data: data);
} catch (e) {
rethrow;
}
}
// Méthode GET générique
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
try {
return await _dio.get(path, queryParameters: queryParameters);
} catch (e) {
rethrow;
}
}
// Méthode PUT générique
Future<Response> put(String path, {dynamic data}) async {
try {
return await _dio.put(path, data: data);
} catch (e) {
rethrow;
}
}
// Méthode DELETE générique
Future<Response> delete(String path) async {
try {
return await _dio.delete(path);
} catch (e) {
rethrow;
}
}
// Authentification avec PHP session
Future<Map<String, dynamic>> login(String username, String password, {required String type}) async {
try {
final response = await _dio.post(AppKeys.loginEndpoint, data: {
'username': username,
'password': password,
'type': type, // Ajouter le type de connexion (user ou admin)
});
// Vérifier la structure de la réponse
final data = response.data as Map<String, dynamic>;
final status = data['status'] as String?;
// Afficher le message en cas d'erreur
if (status != 'success') {
final message = data['message'] as String?;
debugPrint('Erreur d\'authentification: $message');
}
// Si le statut est 'success', récupérer le session_id
if (status == 'success' && data.containsKey('session_id')) {
final sessionId = data['session_id'];
// Définir la session pour les futures requêtes
if (sessionId != null) {
setSessionId(sessionId);
}
}
return data;
} catch (e) {
rethrow;
}
}
// Déconnexion
Future<void> logout() async {
try {
if (_sessionId != null) {
await _dio.post(AppKeys.logoutEndpoint);
_sessionId = null;
}
} catch (e) {
// Même en cas d'erreur, on réinitialise la session
_sessionId = null;
rethrow;
}
}
// Utilisateurs
Future<List<UserModel>> getUsers() async {
try {
final response = await retry(
() => _dio.get('/users'),
retryIf: (e) => e is SocketException || e is TimeoutException,
);
return (response.data as List).map((json) => UserModel.fromJson(json)).toList();
} catch (e) {
// Gérer les erreurs
rethrow;
}
}
Future<UserModel> getUserById(int id) async {
try {
final response = await _dio.get('/users/$id');
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<UserModel> createUser(UserModel user) async {
try {
final response = await _dio.post('/users', data: user.toJson());
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<UserModel> updateUser(UserModel user) async {
try {
final response = await _dio.put('/users/${user.id}', data: user.toJson());
return UserModel.fromJson(response.data);
} catch (e) {
rethrow;
}
}
Future<void> deleteUser(String id) async {
try {
await _dio.delete('/users/$id');
} catch (e) {
rethrow;
}
}
// Espace réservé pour les futures méthodes de gestion des profils
// Espace réservé pour les futures méthodes de gestion des données
// Synchronisation en batch
Future<Map<String, dynamic>> syncData({
List<UserModel>? users,
}) async {
try {
final Map<String, dynamic> payload = {
if (users != null) 'users': users.map((u) => u.toJson()).toList(),
};
final response = await _dio.post('/sync', data: payload);
return response.data;
} catch (e) {
rethrow;
}
}
} }

View File

@@ -53,135 +53,67 @@ class DataLoadingService extends ChangeNotifier {
Box<MessageModel> get _chatMessageBox => Hive.box<MessageModel>(AppKeys.chatMessagesBoxName); Box<MessageModel> get _chatMessageBox => Hive.box<MessageModel>(AppKeys.chatMessagesBoxName);
Box get _settingsBox => Hive.box(AppKeys.settingsBoxName); Box get _settingsBox => Hive.box(AppKeys.settingsBoxName);
// === NETTOYAGE ET PRÉPARATION DES DONNÉES ===
/// Nettoie toutes les données avant le login
Future<void> cleanDataBeforeLogin() async {
try {
_updateLoadingState(LoadingState.initial.copyWith(
progress: 0.05,
message: 'Nettoyage des données...',
stepDescription: 'Suppression des données obsolètes',
));
debugPrint('🧹 Début du nettoyage des données avant login...');
// Étape 1: Nettoyage des boîtes non référencées (5%)
await _cleanNonDefinedBoxes();
// Étape 2: Nettoyage sécurisé des données (10%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.10,
stepDescription: 'Préparation du stockage local',
));
if (kIsWeb) {
await HiveWebFix.safeCleanHiveBoxes(excludeBoxes: [AppKeys.userBoxName]);
} else if (Platform.isIOS) {
await _cleanHiveFilesOnIOS();
} else if (Platform.isAndroid) {
await _cleanHiveFilesOnAndroid();
}
// Étape 3: Recréation des boîtes (15%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.15,
stepDescription: 'Initialisation des bases de données',
));
await _clearAndRecreateBoxes();
await _initializeBoxes();
debugPrint('✅ Nettoyage des données terminé');
} catch (e) {
debugPrint('❌ Erreur lors du nettoyage des données: $e');
_updateLoadingState(LoadingState.error('Erreur lors du nettoyage: $e'));
rethrow;
}
}
/// Traite toutes les données reçues de l'API lors du login /// Traite toutes les données reçues de l'API lors du login
/// Les boxes sont déjà propres, on charge juste les données
Future<void> processLoginData(Map<String, dynamic> apiResult) async { Future<void> processLoginData(Map<String, dynamic> apiResult) async {
try { try {
debugPrint('📊 Début du traitement des données de login...'); debugPrint('📊 Début du chargement des données (boxes déjà propres)...');
// Étape 4: Traitement des clients (35%) // Vérifier que les boxes sont ouvertes
_updateLoadingState(_loadingState.copyWith( _verifyBoxesAreOpen();
progress: 0.35,
stepDescription: 'Chargement des clients',
));
// Charger les données directement (les boxes sont vides et propres)
if (apiResult['clients'] != null) { if (apiResult['clients'] != null) {
await _processClients(apiResult['clients']); await _processClients(apiResult['clients']);
} }
// Étape 5: Traitement des opérations (50%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.50,
stepDescription: 'Chargement des opérations',
));
if (apiResult['operations'] != null) { if (apiResult['operations'] != null) {
await _processOperations(apiResult['operations']); await _processOperations(apiResult['operations']);
} }
// Étape 6: Traitement des secteurs (65%) if (apiResult['secteurs'] != null) {
_updateLoadingState(_loadingState.copyWith( await _processSectors(apiResult['secteurs']);
progress: 0.65,
stepDescription: 'Chargement des secteurs',
));
if (apiResult['sectors'] != null) {
await _processSectors(apiResult['sectors']);
} }
// Étape 7: Traitement des passages (75%)
_updateLoadingState(_loadingState.copyWith(
progress: 0.75,
stepDescription: 'Chargement des passages',
));
if (apiResult['passages'] != null) { if (apiResult['passages'] != null) {
await _processPassages(apiResult['passages']); await _processPassages(apiResult['passages']);
} }
// Étape 8: Traitement des membres (85%) if (apiResult['amicale'] != null) {
_updateLoadingState(_loadingState.copyWith( await _processAmicale(apiResult['amicale']);
progress: 0.85, }
stepDescription: 'Chargement des membres', if (apiResult['membres'] != null) {
)); await _processMembres(apiResult['membres']);
final membresData = apiResult['membres'] ?? apiResult['members'];
if (membresData != null) {
await _processMembres(membresData);
} }
// Étape 9: Traitement des associations utilisateurs-secteurs (95%) if (apiResult['userSecteurs'] != null) {
_updateLoadingState(_loadingState.copyWith( await _processUserSectors(apiResult['userSecteurs']);
progress: 0.95,
stepDescription: 'Finalisation du chargement',
));
if (apiResult['users_sectors'] != null) {
await _processUserSectors(apiResult['users_sectors']);
} }
// Étape finale: Chargement terminé (100%)
_updateLoadingState(LoadingState.completed.copyWith(
progress: 1.0,
message: 'Chargement terminé avec succès',
stepDescription: 'Données synchronisées',
));
debugPrint('✅ Traitement des données de login terminé');
_logDataSummary();
} catch (e) { } catch (e) {
debugPrint('❌ Erreur lors du traitement des données de login: $e'); debugPrint('❌ Erreur lors du chargement: $e');
_updateLoadingState(LoadingState.error('Erreur lors du chargement: $e'));
rethrow; rethrow;
} }
} }
void _verifyBoxesAreOpen() {
final requiredBoxes = [
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.amicaleBoxName,
];
for (final boxName in requiredBoxes) {
if (!Hive.isBoxOpen(boxName)) {
throw Exception('La boîte $boxName n\'est pas ouverte. Redémarrez l\'application.');
}
}
debugPrint('✅ Toutes les boîtes requises sont ouvertes');
}
/// Nettoie complètement toutes les données lors du logout /// Nettoie complètement toutes les données lors du logout
Future<void> cleanDataAfterLogout() async { Future<void> cleanDataAfterLogout() async {
try { try {
@@ -194,117 +126,6 @@ class DataLoadingService extends ChangeNotifier {
} }
} }
// === MÉTHODES PRIVÉES DE NETTOYAGE ===
Future<void> _cleanNonDefinedBoxes() async {
final nonDefinedBoxes = ['auth', 'locations', 'messages'];
for (final boxName in nonDefinedBoxes) {
try {
if (Hive.isBoxOpen(boxName)) {
debugPrint('Fermeture de la boîte non référencée: $boxName');
await Hive.box(boxName).close();
}
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Box $boxName supprimée');
} catch (e) {
debugPrint('⚠️ Erreur suppression box $boxName: $e');
}
}
}
Future<void> _clearAndRecreateBoxes() async {
try {
debugPrint('🔄 Recréation des boîtes Hive...');
final boxesToDelete = [
AppKeys.passagesBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.userSectorBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
];
// Vider chaque boîte sans la fermer
for (final boxName in boxesToDelete) {
try {
if (Hive.isBoxOpen(boxName)) {
final box = Hive.box(boxName);
await box.clear();
debugPrint('✅ Box $boxName vidée');
} else {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Box $boxName supprimée du disque');
}
} catch (e) {
debugPrint('⚠️ Erreur nettoyage box $boxName: $e');
}
}
await Future.delayed(const Duration(milliseconds: 500));
} catch (e) {
debugPrint('❌ Erreur recréation des boîtes: $e');
rethrow;
}
}
Future<void> _initializeBoxes() async {
debugPrint('📦 Initialisation des boîtes Hive...');
final boxesToInit = [
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
];
for (final boxName in boxesToInit) {
await _ensureBoxIsOpen(boxName);
}
debugPrint('✅ Toutes les boîtes Hive sont ouvertes');
}
Future<void> _ensureBoxIsOpen(String boxName) async {
try {
if (!Hive.isBoxOpen(boxName)) {
debugPrint('Ouverture de la boîte $boxName...');
switch (boxName) {
case AppKeys.passagesBoxName:
await Hive.openBox<PassageModel>(boxName);
break;
case AppKeys.operationsBoxName:
await Hive.openBox<OperationModel>(boxName);
break;
case AppKeys.sectorsBoxName:
await Hive.openBox<SectorModel>(boxName);
break;
case AppKeys.membresBoxName:
await Hive.openBox<MembreModel>(boxName);
break;
case AppKeys.userSectorBoxName:
await Hive.openBox<UserSectorModel>(boxName);
break;
case AppKeys.chatConversationsBoxName:
await Hive.openBox<ConversationModel>(boxName);
break;
case AppKeys.chatMessagesBoxName:
await Hive.openBox<MessageModel>(boxName);
break;
default:
await Hive.openBox(boxName);
}
}
} catch (e) {
debugPrint('❌ Erreur ouverture box $boxName: $e');
throw Exception('Impossible d\'ouvrir la boîte $boxName: $e');
}
}
// === MÉTHODES DE TRAITEMENT DES DONNÉES === // === MÉTHODES DE TRAITEMENT DES DONNÉES ===
Future<void> _processClients(dynamic clientsData) async { Future<void> _processClients(dynamic clientsData) async {
@@ -446,6 +267,33 @@ class DataLoadingService extends ChangeNotifier {
} }
} }
// Ajouter cette nouvelle méthode pour traiter l'amicale unique
Future<void> _processAmicale(dynamic amicaleData) async {
try {
debugPrint('🏢 Traitement de l\'amicale unique...');
if (amicaleData == null) {
debugPrint(' Aucune donnée d\'amicale à traiter');
return;
}
await _amicaleBox.clear();
try {
// Les données d'amicale sont un objet unique
final Map<String, dynamic> amicaleMap = Map<String, dynamic>.from(amicaleData as Map);
final amicale = AmicaleModel.fromJson(amicaleMap);
await _amicaleBox.put(amicale.id, amicale);
debugPrint('✅ Amicale stockée: ${amicale.name} (ID: ${amicale.id})');
} catch (e) {
debugPrint('⚠️ Erreur traitement amicale: $e');
debugPrint('⚠️ Données reçues: $amicaleData');
}
} catch (e) {
debugPrint('❌ Erreur traitement amicale: $e');
}
}
Future<void> _processMembres(dynamic membresData) async { Future<void> _processMembres(dynamic membresData) async {
try { try {
debugPrint('👤 Traitement des membres...'); debugPrint('👤 Traitement des membres...');
@@ -625,41 +473,6 @@ class DataLoadingService extends ChangeNotifier {
} }
} }
// === MÉTHODES UTILITAIRES ===
/// Affiche un résumé des données chargées
void _logDataSummary() {
try {
debugPrint('📊 === RÉSUMÉ DES DONNÉES CHARGÉES ===');
debugPrint('Opérations: ${_operationBox.length}');
debugPrint('Secteurs: ${_sectorBox.length}');
debugPrint('Passages: ${_passageBox.length}');
debugPrint('Membres: ${_membreBox.length}');
debugPrint('Associations User-Sector: ${_userSectorBox.length}');
debugPrint('Amicales: ${_amicaleBox.length}');
debugPrint('=================================');
} catch (e) {
debugPrint('⚠️ Erreur lors du résumé: $e');
}
}
/// Retourne un résumé des données pour l'UI
Map<String, int> getDataSummary() {
try {
return {
'operations': _operationBox.length,
'sectors': _sectorBox.length,
'passages': _passageBox.length,
'membres': _membreBox.length,
'userSectors': _userSectorBox.length,
'amicales': _amicaleBox.length,
};
} catch (e) {
debugPrint('⚠️ Erreur génération résumé: $e');
return {};
}
}
// === RESET POUR TESTS === // === RESET POUR TESTS ===
static void reset() { static void reset() {
_instance = null; _instance = null;

View File

@@ -0,0 +1,65 @@
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/data/models/user_model.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/data/models/client_model.dart';
import 'package:geosector_app/core/data/models/operation_model.dart';
import 'package:geosector_app/core/data/models/sector_model.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/data/models/membre_model.dart';
import 'package:geosector_app/core/data/models/user_sector_model.dart';
import 'package:geosector_app/core/data/models/region_model.dart';
import 'package:geosector_app/chat/models/chat_adapters.dart';
class HiveAdapters {
/// Enregistre tous les TypeAdapters nécessaires
static void registerAll() {
// Modèles principaux
if (!Hive.isAdapterRegistered(0)) {
Hive.registerAdapter(UserModelAdapter());
}
if (!Hive.isAdapterRegistered(1)) {
Hive.registerAdapter(OperationModelAdapter());
}
if (!Hive.isAdapterRegistered(3)) {
Hive.registerAdapter(SectorModelAdapter());
}
if (!Hive.isAdapterRegistered(4)) {
Hive.registerAdapter(PassageModelAdapter());
}
if (!Hive.isAdapterRegistered(5)) {
Hive.registerAdapter(MembreModelAdapter());
}
if (!Hive.isAdapterRegistered(6)) {
Hive.registerAdapter(UserSectorModelAdapter());
}
if (!Hive.isAdapterRegistered(7)) {
Hive.registerAdapter(RegionModelAdapter());
}
if (!Hive.isAdapterRegistered(10)) {
Hive.registerAdapter(ClientModelAdapter());
}
if (!Hive.isAdapterRegistered(11)) {
Hive.registerAdapter(AmicaleModelAdapter());
}
// Chat adapters
if (!Hive.isAdapterRegistered(20)) {
Hive.registerAdapter(ConversationModelAdapter());
}
if (!Hive.isAdapterRegistered(21)) {
Hive.registerAdapter(MessageModelAdapter());
}
if (!Hive.isAdapterRegistered(22)) {
Hive.registerAdapter(ParticipantModelAdapter());
}
if (!Hive.isAdapterRegistered(23)) {
Hive.registerAdapter(AnonymousUserModelAdapter());
}
if (!Hive.isAdapterRegistered(24)) {
Hive.registerAdapter(AudienceTargetModelAdapter());
}
if (!Hive.isAdapterRegistered(25)) {
Hive.registerAdapter(NotificationSettingsAdapter());
}
}
}

View File

@@ -4,22 +4,9 @@ import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:flutter_web_plugins/url_strategy.dart';
import 'package:geosector_app/core/services/app_info_service.dart'; import 'package:geosector_app/core/services/app_info_service.dart';
import 'package:geosector_app/core/services/api_service.dart'; import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/services/current_user_service.dart';
import 'package:geosector_app/core/services/current_amicale_service.dart';
import 'package:geosector_app/app.dart'; import 'package:geosector_app/app.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/data/models/user_model.dart'; import 'package:geosector_app/core/services/hive_adapters.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart';
import 'package:geosector_app/core/data/models/client_model.dart';
import 'package:geosector_app/core/data/models/operation_model.dart';
import 'package:geosector_app/core/data/models/sector_model.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/data/models/membre_model.dart';
import 'package:geosector_app/core/data/models/user_sector_model.dart';
import 'package:geosector_app/core/data/models/region_model.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/services/hive_reset_state_service.dart';
import 'package:geosector_app/chat/models/chat_adapters.dart';
void main() async { void main() async {
// IMPORTANT: Configurer l'URL strategy pour éviter les # dans les URLs // IMPORTANT: Configurer l'URL strategy pour éviter les # dans les URLs
@@ -31,13 +18,7 @@ void main() async {
await _initializeServices(); await _initializeServices();
// Initialiser Hive avec gestion des erreurs // Initialiser Hive avec gestion des erreurs
final hiveInitialized = await _initializeHive(); await _initializeHive();
// TEMPORAIREMENT: Ne pas marquer l'erreur pour éviter la redirection
// if (!hiveInitialized) {
// debugPrint('Incompatibilité détectée dans les données Hive. Marquage pour affichage du dialogue...');
// hiveResetStateService.markAsReset();
// }
// Configurer l'orientation de l'application (mobile uniquement) // Configurer l'orientation de l'application (mobile uniquement)
if (!kIsWeb) { if (!kIsWeb) {
@@ -71,185 +52,16 @@ Future<void> _initializeServices() async {
} }
} }
/// Initialise Hive et les adaptateurs Future<void> _initializeHive() async {
Future<bool> _initializeHive() async {
try { try {
// Initialiser Hive
await Hive.initFlutter(); await Hive.initFlutter();
// Enregistrer les adaptateurs Hive pour les modèles principaux // Enregistrer tous les adapters
_registerHiveAdapters(); HiveAdapters.registerAll();
// Ouvrir uniquement les boîtes essentielles au démarrage debugPrint('✅ Hive et TypeAdapters initialisés');
await _openEssentialHiveBoxes();
// Charger les données depuis Hive au démarrage
await CurrentUserService.instance.loadFromHive();
await CurrentAmicaleService.instance.loadFromHive();
debugPrint('✅ Données utilisateur/amicale chargées depuis Hive');
debugPrint('Hive initialisé avec succès');
return true;
} catch (e) { } catch (e) {
debugPrint('Erreur lors de l\'initialisation de Hive: $e'); debugPrint('Erreur Hive: $e');
return false; rethrow;
}
}
/// Enregistre tous les adaptateurs Hive
void _registerHiveAdapters() {
// Vérifier si les adaptateurs sont déjà enregistrés pour éviter les doublons
if (!Hive.isAdapterRegistered(0)) {
Hive.registerAdapter(UserModelAdapter());
}
if (!Hive.isAdapterRegistered(1)) {
Hive.registerAdapter(AmicaleModelAdapter());
}
if (!Hive.isAdapterRegistered(2)) {
Hive.registerAdapter(ClientModelAdapter());
}
if (!Hive.isAdapterRegistered(3)) {
Hive.registerAdapter(OperationModelAdapter());
}
if (!Hive.isAdapterRegistered(4)) {
Hive.registerAdapter(SectorModelAdapter());
}
if (!Hive.isAdapterRegistered(5)) {
Hive.registerAdapter(PassageModelAdapter());
}
if (!Hive.isAdapterRegistered(6)) {
Hive.registerAdapter(MembreModelAdapter());
}
if (!Hive.isAdapterRegistered(7)) {
Hive.registerAdapter(UserSectorModelAdapter());
}
if (!Hive.isAdapterRegistered(8)) {
Hive.registerAdapter(RegionModelAdapter());
}
// Modèles de chat
if (!Hive.isAdapterRegistered(9)) {
Hive.registerAdapter(ConversationModelAdapter());
}
if (!Hive.isAdapterRegistered(10)) {
Hive.registerAdapter(MessageModelAdapter());
}
if (!Hive.isAdapterRegistered(11)) {
Hive.registerAdapter(ParticipantModelAdapter());
}
if (!Hive.isAdapterRegistered(12)) {
Hive.registerAdapter(AnonymousUserModelAdapter());
}
if (!Hive.isAdapterRegistered(13)) {
Hive.registerAdapter(AudienceTargetModelAdapter());
}
if (!Hive.isAdapterRegistered(14)) {
Hive.registerAdapter(NotificationSettingsAdapter());
}
}
/// Ouvre les boîtes Hive essentielles avec migration users -> user
Future<void> _openEssentialHiveBoxes() async {
final boxesToOpen = [
{'name': AppKeys.userBoxName, 'type': 'UserModel'},
{'name': AppKeys.amicaleBoxName, 'type': 'AmicaleModel'},
{'name': AppKeys.clientsBoxName, 'type': 'ClientModel'},
{'name': AppKeys.settingsBoxName, 'type': 'dynamic'},
{'name': AppKeys.chatConversationsBoxName, 'type': 'ConversationModel'},
{'name': AppKeys.chatMessagesBoxName, 'type': 'MessageModel'},
];
// Logique de migration de l'ancienne box users vers user
try {
// Vérifier si l'ancienne box users existe
if (await _doesBoxExist(AppKeys.usersBoxNameOld)) {
debugPrint('🔄 Migration détectée: box users -> user');
// Ouvrir l'ancienne box
final oldBox = await Hive.openBox<UserModel>(AppKeys.usersBoxNameOld);
// Ouvrir la nouvelle box
final newBox = await Hive.openBox<UserModel>(AppKeys.userBoxName);
// Migrer les données si la nouvelle box est vide
if (oldBox.isNotEmpty && newBox.isEmpty) {
debugPrint('📦 Migration des données users -> user...');
// Chercher l'utilisateur actuel dans l'ancienne box
final userData = oldBox.get('current_user') ?? oldBox.values.firstOrNull;
if (userData != null) {
await newBox.put('current_user', userData);
debugPrint('✅ Migration de users -> user réussie pour ${userData.email}');
}
}
// Fermer et supprimer l'ancienne box
await oldBox.close();
await Hive.deleteBoxFromDisk(AppKeys.usersBoxNameOld);
debugPrint('🗑️ Ancienne box users supprimée');
} else {
// Ouvrir normalement la nouvelle box
await Hive.openBox<UserModel>(AppKeys.userBoxName);
}
} catch (e) {
debugPrint('⚠️ Erreur migration box users: $e');
// En cas d'erreur, ouvrir quand même la nouvelle box
try {
await Hive.openBox<UserModel>(AppKeys.userBoxName);
} catch (e2) {
debugPrint('❌ Impossible d\'ouvrir la box user: $e2');
}
}
// Ouvrir les autres boîtes
for (final box in boxesToOpen) {
try {
final boxName = box['name'] as String;
final boxType = box['type'] as String;
// Skip userBoxName car déjà traité dans la migration
if (boxName == AppKeys.userBoxName) continue;
// Vérifier si la boîte est déjà ouverte
if (Hive.isBoxOpen(boxName)) {
debugPrint('📦 Boîte $boxName déjà ouverte');
continue;
}
switch (boxType) {
case 'AmicaleModel':
await Hive.openBox<AmicaleModel>(boxName);
break;
case 'ClientModel':
await Hive.openBox<ClientModel>(boxName);
break;
case 'ConversationModel':
await Hive.openBox<ConversationModel>(boxName);
break;
case 'MessageModel':
await Hive.openBox<MessageModel>(boxName);
break;
default:
await Hive.openBox(boxName);
}
debugPrint('✅ Boîte $boxName ouverte avec succès');
} catch (e) {
debugPrint('❌ Erreur lors de l\'ouverture de la boîte ${box['name']}: $e');
// Ne pas lancer d'erreur, continuer avec les autres boîtes
}
}
}
/// Vérifie si une box Hive existe sur le disque
Future<bool> _doesBoxExist(String boxName) async {
try {
// Tentative d'ouverture pour vérifier l'existence
final box = await Hive.openBox<UserModel>(boxName);
final exists = box.isNotEmpty;
await box.close();
return exists;
} catch (e) {
return false;
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geosector_app/core/services/api_service.dart';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/data/models/amicale_model.dart'; import 'package:geosector_app/core/data/models/amicale_model.dart';
@@ -64,6 +65,9 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
void _loadCurrentUser() { void _loadCurrentUser() {
final currentUser = widget.userRepository.getCurrentUser(); final currentUser = widget.userRepository.getCurrentUser();
debugPrint('🔍 _loadCurrentUser - Utilisateur: ${currentUser?.username} (ID: ${currentUser?.id})');
debugPrint('🔍 _loadCurrentUser - fkEntite: ${currentUser?.fkEntite}');
if (currentUser == null) { if (currentUser == null) {
setState(() { setState(() {
_errorMessage = 'Utilisateur non connecté'; _errorMessage = 'Utilisateur non connecté';
@@ -78,43 +82,16 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
return; return;
} }
// Vérifier immédiatement si l'amicale existe
final amicale = widget.amicaleRepository.getUserAmicale(currentUser.fkEntite!);
debugPrint('🔍 Amicale trouvée dans le repository: ${amicale?.name ?? 'null'}');
setState(() { setState(() {
_currentUser = currentUser; _currentUser = currentUser;
_errorMessage = null; _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) { void _handleEditMembre(MembreModel membre) {
showDialog( showDialog(
context: context, context: context,
@@ -226,9 +203,19 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
child: ValueListenableBuilder<Box<AmicaleModel>>( child: ValueListenableBuilder<Box<AmicaleModel>>(
valueListenable: widget.amicaleRepository.getAmicalesBox().listenable(), valueListenable: widget.amicaleRepository.getAmicalesBox().listenable(),
builder: (context, amicalesBox, child) { builder: (context, amicalesBox, child) {
debugPrint('🔍 AmicalesBox - Nombre d\'amicales: ${amicalesBox.length}');
debugPrint('🔍 AmicalesBox - Clés disponibles: ${amicalesBox.keys.toList()}');
debugPrint('🔍 Recherche amicale avec fkEntite: ${_currentUser!.fkEntite}');
final amicale = amicalesBox.get(_currentUser!.fkEntite!); final amicale = amicalesBox.get(_currentUser!.fkEntite!);
debugPrint('🔍 Amicale récupérée: ${amicale?.name ?? 'AUCUNE'}');
if (amicale == null) { if (amicale == null) {
// Ajouter plus d'informations de debug
debugPrint('❌ PROBLÈME: Amicale non trouvée');
debugPrint('❌ fkEntite recherché: ${_currentUser!.fkEntite}');
debugPrint('❌ Contenu de la box: ${amicalesBox.values.map((a) => '${a.id}: ${a.name}').join(', ')}');
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -245,7 +232,7 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'L\'amicale associée à votre compte n\'existe plus.', 'L\'amicale associée à votre compte n\'existe plus.\nfkEntite: ${_currentUser!.fkEntite}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: theme.textTheme.bodyLarge, style: theme.textTheme.bodyLarge,
), ),
@@ -293,7 +280,7 @@ class _AdminAmicalePageState extends State<AdminAmicalePage> {
onDelete: null, onDelete: null,
amicaleRepository: widget.amicaleRepository, amicaleRepository: widget.amicaleRepository,
userRepository: widget.userRepository, userRepository: widget.userRepository,
apiService: null, // Ou passez l'ApiService si vous l'avez disponible apiService: ApiService.instance,
showActionsColumn: false, showActionsColumn: false,
), ),
), ),

View File

@@ -68,10 +68,9 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
} }
} catch (e) { } catch (e) {
debugPrint('Erreur lors de la récupération de la version: $e'); debugPrint('Erreur lors de la récupération de la version: $e');
// Fallback sur la version du AppInfoService si elle existe
if (mounted) { if (mounted) {
setState(() { setState(() {
_appVersion = AppInfoService.fullVersion.split(' ').last; // Extraire juste le numéro _appVersion = AppInfoService.fullVersion.split(' ').last;
}); });
} }
} }
@@ -81,29 +80,24 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
void initState() { void initState() {
super.initState(); super.initState();
// Animation controller sur 5 secondes (augmenté de 3 à 5 secondes) // Animation controller
_animationController = AnimationController( _animationController = AnimationController(
vsync: this, vsync: this,
duration: const Duration(seconds: 5), duration: const Duration(seconds: 5),
); );
// Animation de 4x la taille à 1x la taille (augmenté de 3x à 4x)
_scaleAnimation = Tween<double>( _scaleAnimation = Tween<double>(
begin: 4.0, // Commencer à 4x la taille begin: 4.0,
end: 1.0, // Terminer à la taille normale end: 1.0,
).animate( ).animate(
CurvedAnimation( CurvedAnimation(
parent: _animationController, parent: _animationController,
curve: Curves.easeOutBack, // Curve pour un effet de rebond curve: Curves.easeOutBack,
), ),
); );
// Démarrer l'animation immédiatement
_animationController.forward(); _animationController.forward();
_getAppVersion(); _getAppVersion();
// Simuler le processus d'initialisation
_startInitialization(); _startInitialization();
} }
@@ -114,151 +108,337 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
} }
void _startInitialization() async { void _startInitialization() async {
// Étape 1: Initialisation des boîtes Hive (0% à 75%) // Table rase complète et recréation propre
if (mounted) { await _completeReset();
setState(() {
_statusMessage = "Initialisation des données..."; // Finalisation
_progress = 0.0;
});
}
// Initialiser toutes les boîtes Hive
await _initializeAllHiveBoxes();
await Future.delayed(const Duration(milliseconds: 500));
// Étape 2: Initialisation des services (75% à 100%)
if (mounted) {
setState(() {
_statusMessage = "Préparation de l'application...";
_progress = 0.75;
});
}
await Future.delayed(const Duration(milliseconds: 500));
if (mounted) { if (mounted) {
setState(() { setState(() {
_statusMessage = "Application prête !";
_progress = 1.0;
_isInitializing = false; _isInitializing = false;
_showButtons = true; _showButtons = true;
_progress = 1.0; // S'assurer que la barre est à 100%
}); });
} }
} }
// Méthode pour initialiser toutes les boîtes Hive /// RESET COMPLET : Destruction totale et recréation propre
Future<void> _initializeAllHiveBoxes() async { Future<void> _completeReset() async {
try { try {
debugPrint('Initialisation de toutes les boîtes Hive...'); debugPrint('🧹 RESET COMPLET : Destruction totale des données Hive...');
// Structure pour les boîtes à ouvrir avec leurs noms d'affichage // Étape 1: Sauvegarder les utilisateurs existants (optionnel)
final boxesToOpen = [ Map<dynamic, UserModel>? existingUsers;
{'name': AppKeys.userBoxName, 'display': 'Préparation utilisateurs'},
{'name': AppKeys.amicaleBoxName, 'display': 'Préparation amicale'},
{'name': AppKeys.clientsBoxName, 'display': 'Préparation clients'},
{'name': AppKeys.regionsBoxName, 'display': 'Préparation régions'},
{'name': AppKeys.operationsBoxName, 'display': 'Préparation opérations'},
{'name': AppKeys.sectorsBoxName, 'display': 'Préparation secteurs'},
{'name': AppKeys.passagesBoxName, 'display': 'Préparation passages'},
{'name': AppKeys.membresBoxName, 'display': 'Préparation membres'},
{'name': AppKeys.userSectorBoxName, 'display': 'Préparation secteurs utilisateurs'},
{'name': AppKeys.settingsBoxName, 'display': 'Préparation paramètres'},
{'name': AppKeys.chatConversationsBoxName, 'display': 'Préparation conversations'},
{'name': AppKeys.chatMessagesBoxName, 'display': 'Préparation messages'},
];
// Calculer l'incrément de progression pour chaque boîte (0.75 / nombre de boîtes) if (mounted) {
final progressIncrement = 0.75 / boxesToOpen.length; setState(() {
double currentProgress = 0.0; _statusMessage = "Sauvegarde des utilisateurs...";
_progress = 0.05;
});
}
// Ouvrir chaque boîte si elle n'est pas déjà ouverte try {
for (int i = 0; i < boxesToOpen.length; i++) { if (Hive.isBoxOpen(AppKeys.userBoxName)) {
final boxName = boxesToOpen[i]['name'] as String; final userBox = Hive.box<UserModel>(AppKeys.userBoxName);
final displayName = boxesToOpen[i]['display'] as String; existingUsers = Map.from(userBox.toMap());
debugPrint('📦 ${existingUsers.length} utilisateurs sauvegardés');
}
} catch (e) {
debugPrint('⚠️ Erreur sauvegarde utilisateurs: $e');
existingUsers = null;
}
// Mettre à jour la barre de progression et le message // Étape 2: DESTRUCTION RADICALE - Fermer tout ce qui peut être ouvert
currentProgress = progressIncrement * (i + 1); if (mounted) {
setState(() {
_statusMessage = "Fermeture de toutes les bases de données...";
_progress = 0.15;
});
}
await _closeAllKnownBoxes();
// Étape 3: DESTRUCTION RADICALE - Supprimer tout Hive du disque
if (mounted) {
setState(() {
_statusMessage = "Suppression complète des anciennes données...";
_progress = 0.25;
});
}
await _nukeHiveCompletely();
// Étape 4: RECRÉATION PROPRE
if (mounted) {
setState(() {
_statusMessage = "Création des nouvelles bases de données...";
_progress = 0.40;
});
}
await _createAllBoxesFresh();
// Étape 5: Restaurer les utilisateurs (optionnel)
if (existingUsers != null && existingUsers.isNotEmpty) {
if (mounted) { if (mounted) {
setState(() { setState(() {
_statusMessage = displayName; _statusMessage = "Restauration des utilisateurs...";
_progress = currentProgress; _progress = 0.80;
}); });
} }
if (!Hive.isBoxOpen(boxName)) { await _restoreUsers(existingUsers);
debugPrint('Ouverture de la boîte $boxName ($displayName)...');
// Ouvrir la boîte avec le type approprié
if (boxName == AppKeys.userBoxName) {
await Hive.openBox<UserModel>(boxName);
} else if (boxName == AppKeys.amicaleBoxName) {
await Hive.openBox<AmicaleModel>(boxName);
} else if (boxName == AppKeys.clientsBoxName) {
await Hive.openBox<ClientModel>(boxName);
} else if (boxName == AppKeys.regionsBoxName) {
// Ouvrir la boîte des régions sans type spécifique pour l'instant
// car RegionModelAdapter n'est pas encore enregistré
await Hive.openBox(boxName);
} else if (boxName == AppKeys.operationsBoxName) {
await Hive.openBox<OperationModel>(boxName);
} else if (boxName == AppKeys.sectorsBoxName) {
await Hive.openBox<SectorModel>(boxName);
} else if (boxName == AppKeys.passagesBoxName) {
await Hive.openBox<PassageModel>(boxName);
} else if (boxName == AppKeys.membresBoxName) {
await Hive.openBox<MembreModel>(boxName);
} else if (boxName == AppKeys.userSectorBoxName) {
await Hive.openBox<UserSectorModel>(boxName);
} else if (boxName == AppKeys.chatConversationsBoxName) {
await Hive.openBox<ConversationModel>(boxName);
} else if (boxName == AppKeys.chatMessagesBoxName) {
await Hive.openBox<MessageModel>(boxName);
} else {
await Hive.openBox(boxName);
}
debugPrint('Boîte $boxName ouverte avec succès');
} else {
debugPrint('Boîte $boxName déjà ouverte');
}
// Ajouter une temporisation entre chaque ouverture
await Future.delayed(const Duration(milliseconds: 500));
} }
// Mettre à jour la barre de progression à 0.2 (20%) à la fin // Étape 6: Vérification finale
if (mounted) { if (mounted) {
setState(() { setState(() {
_statusMessage = 'Toutes les boîtes sont prêtes'; _statusMessage = "Vérification des bases de données...";
_progress = 0.8; _progress = 0.90;
}); });
await Future.delayed(const Duration(milliseconds: 500));
} }
debugPrint('✅ RESET COMPLET terminé avec succès');
} catch (e) {
debugPrint('❌ Erreur lors du reset complet: $e');
if (mounted) { if (mounted) {
setState(() { setState(() {
_statusMessage = "Préparation de l'application..."; _statusMessage = "Erreur critique - Redémarrage recommandé";
_progress = 0.9; _progress = 1.0;
});
await Future.delayed(const Duration(milliseconds: 500));
}
// Finalisation
if (mounted) {
setState(() {
_isInitializing = false; _isInitializing = false;
_showButtons = true; _showButtons = true;
_progress = 1.0;
}); });
} }
debugPrint('Toutes les boîtes Hive sont maintenant ouvertes'); }
} catch (e) { }
debugPrint('Erreur lors de l\'initialisation des boîtes Hive: $e');
// En cas d'erreur, mettre à jour le message /// Ferme toutes les boîtes connues
if (mounted) { Future<void> _closeAllKnownBoxes() async {
setState(() { try {
_statusMessage = 'Erreur lors de l\'initialisation des données'; final allKnownBoxes = [
}); AppKeys.userBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.regionsBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.settingsBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
// Boîtes potentiellement problématiques
'auth', 'locations', 'messages', 'temp'
];
debugPrint('🔒 Fermeture de ${allKnownBoxes.length} boîtes connues...');
for (final boxName in allKnownBoxes) {
try {
if (Hive.isBoxOpen(boxName)) {
await Hive.box(boxName).close();
debugPrint('✅ Boîte $boxName fermée');
}
} catch (e) {
debugPrint('⚠️ Erreur fermeture $boxName: $e');
// Continuer même en cas d'erreur
}
} }
await Future.delayed(const Duration(milliseconds: 1000));
} catch (e) {
debugPrint('❌ Erreur fermeture des boîtes: $e');
}
}
/// Suppression RADICALE de tout Hive
Future<void> _nukeHiveCompletely() async {
try {
debugPrint('💥 DESTRUCTION NUCLÉAIRE de Hive...');
if (kIsWeb) {
// En version web, supprimer toutes les boîtes possibles une par une
final allPossibleBoxes = [
AppKeys.userBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.regionsBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.settingsBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
// Toutes les boîtes potentiellement corrompues
'auth', 'locations', 'messages', 'temp', 'cache', 'data'
];
for (final boxName in allPossibleBoxes) {
try {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Boîte $boxName DÉTRUITE');
} catch (e) {
debugPrint('⚠️ Erreur destruction $boxName: $e');
}
}
} else {
// Sur mobile/desktop, destruction totale
try {
await Hive.deleteFromDisk();
debugPrint('✅ Hive COMPLÈTEMENT DÉTRUIT');
} catch (e) {
debugPrint('⚠️ Erreur destruction totale: $e');
// Fallback : supprimer boîte par boîte
await _deleteBoxesOneByOne();
}
}
// Attendre pour s'assurer que tout est détruit
await Future.delayed(const Duration(seconds: 2));
} catch (e) {
debugPrint('❌ Erreur destruction Hive: $e');
}
}
/// Fallback : supprimer les boîtes une par une
Future<void> _deleteBoxesOneByOne() async {
final allBoxes = [
AppKeys.userBoxName,
AppKeys.amicaleBoxName,
AppKeys.clientsBoxName,
AppKeys.regionsBoxName,
AppKeys.operationsBoxName,
AppKeys.sectorsBoxName,
AppKeys.passagesBoxName,
AppKeys.membresBoxName,
AppKeys.userSectorBoxName,
AppKeys.settingsBoxName,
AppKeys.chatConversationsBoxName,
AppKeys.chatMessagesBoxName,
];
for (final boxName in allBoxes) {
try {
await Hive.deleteBoxFromDisk(boxName);
debugPrint('✅ Boîte $boxName supprimée (fallback)');
} catch (e) {
debugPrint('⚠️ Erreur suppression fallback $boxName: $e');
}
}
}
/// Recrée toutes les boîtes VIDES et PROPRES
Future<void> _createAllBoxesFresh() async {
try {
debugPrint('🆕 Création de toutes les boîtes vides...');
final boxesToCreate = [
{'name': AppKeys.userBoxName, 'type': 'UserModel'},
{'name': AppKeys.amicaleBoxName, 'type': 'AmicaleModel'},
{'name': AppKeys.clientsBoxName, 'type': 'ClientModel'},
{'name': AppKeys.regionsBoxName, 'type': 'dynamic'},
{'name': AppKeys.operationsBoxName, 'type': 'OperationModel'},
{'name': AppKeys.sectorsBoxName, 'type': 'SectorModel'},
{'name': AppKeys.passagesBoxName, 'type': 'PassageModel'},
{'name': AppKeys.membresBoxName, 'type': 'MembreModel'},
{'name': AppKeys.userSectorBoxName, 'type': 'UserSectorModel'},
{'name': AppKeys.settingsBoxName, 'type': 'dynamic'},
{'name': AppKeys.chatConversationsBoxName, 'type': 'ConversationModel'},
{'name': AppKeys.chatMessagesBoxName, 'type': 'MessageModel'},
];
final progressIncrement = 0.35 / boxesToCreate.length; // De 0.40 à 0.75
for (int i = 0; i < boxesToCreate.length; i++) {
final boxInfo = boxesToCreate[i];
final boxName = boxInfo['name'] as String;
final boxType = boxInfo['type'] as String;
if (mounted) {
setState(() {
_statusMessage = "Création de $boxName...";
_progress = 0.40 + (progressIncrement * i);
});
}
try {
// Créer la boîte avec le bon type
switch (boxType) {
case 'UserModel':
await Hive.openBox<UserModel>(boxName);
break;
case 'AmicaleModel':
await Hive.openBox<AmicaleModel>(boxName);
break;
case 'ClientModel':
await Hive.openBox<ClientModel>(boxName);
break;
case 'OperationModel':
await Hive.openBox<OperationModel>(boxName);
break;
case 'SectorModel':
await Hive.openBox<SectorModel>(boxName);
break;
case 'PassageModel':
await Hive.openBox<PassageModel>(boxName);
break;
case 'MembreModel':
await Hive.openBox<MembreModel>(boxName);
break;
case 'UserSectorModel':
await Hive.openBox<UserSectorModel>(boxName);
break;
case 'ConversationModel':
await Hive.openBox<ConversationModel>(boxName);
break;
case 'MessageModel':
await Hive.openBox<MessageModel>(boxName);
break;
default:
await Hive.openBox(boxName);
}
debugPrint('✅ Boîte $boxName créée (type: $boxType)');
} catch (e) {
debugPrint('❌ Erreur création $boxName: $e');
// En cas d'erreur, essayer sans type
try {
await Hive.openBox(boxName);
debugPrint('⚠️ Boîte $boxName créée sans type');
} catch (e2) {
debugPrint('❌ Échec total création $boxName: $e2');
}
}
await Future.delayed(const Duration(milliseconds: 200));
}
} catch (e) {
debugPrint('❌ Erreur création des boîtes: $e');
}
}
/// Restaure les utilisateurs sauvegardés
Future<void> _restoreUsers(Map<dynamic, UserModel> users) async {
try {
if (Hive.isBoxOpen(AppKeys.userBoxName)) {
final userBox = Hive.box<UserModel>(AppKeys.userBoxName);
for (final entry in users.entries) {
try {
await userBox.put(entry.key, entry.value);
} catch (e) {
debugPrint('⚠️ Erreur restauration utilisateur ${entry.key}: $e');
}
}
debugPrint('${users.length} utilisateurs restaurés');
}
} catch (e) {
debugPrint('❌ Erreur restauration utilisateurs: $e');
} }
} }
@@ -295,7 +475,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
children: [ children: [
const Spacer(flex: 2), const Spacer(flex: 2),
// Logo avec animation de réduction // Logo avec animation
AnimatedBuilder( AnimatedBuilder(
animation: _scaleAnimation, animation: _scaleAnimation,
builder: (context, child) { builder: (context, child) {
@@ -306,13 +486,13 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
}, },
child: Image.asset( child: Image.asset(
'assets/images/logo-geosector-1024.png', 'assets/images/logo-geosector-1024.png',
height: 180, // Augmenté de 140 à 180 height: 180,
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// Titre avec animation fade-in // Titre
AnimatedOpacity( AnimatedOpacity(
opacity: _isInitializing ? 0.9 : 1.0, opacity: _isInitializing ? 0.9 : 1.0,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
@@ -328,7 +508,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
const SizedBox(height: 16), const SizedBox(height: 16),
// Sous-titre avec nouveau slogan // Sous-titre
AnimatedOpacity( AnimatedOpacity(
opacity: _isInitializing ? 0.8 : 1.0, opacity: _isInitializing ? 0.8 : 1.0,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
@@ -356,7 +536,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
valueColor: AlwaysStoppedAnimation<Color>( valueColor: AlwaysStoppedAnimation<Color>(
theme.colorScheme.primary, theme.colorScheme.primary,
), ),
minHeight: 10, // Augmenté de 6 à 10 minHeight: 10,
), ),
), ),
), ),
@@ -369,7 +549,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
), ),
], ],
// Boutons après l'initialisation // Boutons (reste identique)
if (_showButtons) ...[ if (_showButtons) ...[
// Bouton Connexion Utilisateur // Bouton Connexion Utilisateur
AnimatedOpacity( AnimatedOpacity(
@@ -377,7 +557,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
context.go('/login/user'); // Utiliser la route spécifique context.go('/login/user');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.green, backgroundColor: Colors.green,
@@ -402,13 +582,13 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
// Bouton Connexion Administrateur // Bouton Connexion Administrateur
AnimatedOpacity( AnimatedOpacity(
opacity: _showButtons ? 1.0 : 0.0, opacity: _showButtons ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
context.go('/login/admin'); // Utiliser la route spécifique context.go('/login/admin');
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.red, backgroundColor: Colors.red,
@@ -432,7 +612,7 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
), ),
), ),
const SizedBox(height: 32), // 2 espaces sous le bouton précédent const SizedBox(height: 32),
// Bouton d'inscription // Bouton d'inscription
AnimatedOpacity( AnimatedOpacity(
@@ -472,7 +652,6 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
child: TextButton.icon( child: TextButton.icon(
onPressed: () { onPressed: () {
// Déterminer l'URL du site web en fonction de l'environnement
String webUrl = 'https://geosector.fr'; String webUrl = 'https://geosector.fr';
if (kIsWeb) { if (kIsWeb) {
@@ -486,7 +665,6 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
} }
} }
// Ouvrir l'URL dans une nouvelle fenêtre/onglet
launchUrl( launchUrl(
Uri.parse(webUrl), Uri.parse(webUrl),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
@@ -514,7 +692,8 @@ class _SplashPageState extends State<SplashPage> with SingleTickerProviderStateM
), ),
), ),
), ),
// Badge de version en bas à droite
// Badge de version
if (_appVersion.isNotEmpty) if (_appVersion.isNotEmpty)
Positioned( Positioned(
bottom: 16, bottom: 16,

View File

@@ -98,14 +98,23 @@ class _AmicaleFormState extends State<AmicaleForm> {
// Appeler l'API pour mettre à jour l'entité // Appeler l'API pour mettre à jour l'entité
Future<void> _updateAmicale(AmicaleModel amicale) async { Future<void> _updateAmicale(AmicaleModel amicale) async {
if (!mounted) return;
try { try {
// Afficher un indicateur de chargement // Afficher un indicateur de chargement
showDialog( showDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
builder: (BuildContext context) { builder: (BuildContext context) {
return const Center( return const AlertDialog(
child: CircularProgressIndicator(), content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Mise à jour en cours...'),
],
),
); );
}, },
); );
@@ -121,9 +130,9 @@ class _AmicaleFormState extends State<AmicaleForm> {
'phone': amicale.phone, 'phone': amicale.phone,
'mobile': amicale.mobile, 'mobile': amicale.mobile,
'email': amicale.email, 'email': amicale.email,
'chk_copie_mail_recu': amicale.chkCopieMailRecu, 'chk_copie_mail_recu': amicale.chkCopieMailRecu ? 1 : 0,
'chk_accept_sms': amicale.chkAcceptSms, 'chk_accept_sms': amicale.chkAcceptSms ? 1 : 0,
'chk_stripe': amicale.chkStripe, 'chk_stripe': amicale.chkStripe ? 1 : 0,
}; };
// 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
@@ -132,63 +141,81 @@ class _AmicaleFormState extends State<AmicaleForm> {
data['gps_lat'] = amicale.gpsLat; data['gps_lat'] = amicale.gpsLat;
data['gps_lng'] = amicale.gpsLng; data['gps_lng'] = amicale.gpsLng;
data['stripe_id'] = amicale.stripeId; data['stripe_id'] = amicale.stripeId;
data['chk_demo'] = amicale.chkDemo; data['chk_demo'] = amicale.chkDemo ? 1 : 0;
data['chk_active'] = amicale.chkActive; data['chk_active'] = amicale.chkActive ? 1 : 0;
} }
// Fermer l'indicateur de chargement debugPrint('🔧 Données à envoyer à l\'API: $data');
Navigator.of(context).pop();
bool apiSuccess = false;
String? errorMessage;
// Appeler l'API si le service est disponible // Appeler l'API si le service est disponible
if (widget.apiService != null) { if (widget.apiService != null) {
try { try {
await widget.apiService!.post('/entite/update', data: data); debugPrint('📡 Appel API pour mise à jour amicale...');
// Afficher un message de succès // Version RESTful correcte avec PUT
if (mounted) { final response = await widget.apiService!.put('/entites/${amicale.id}', data: data);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( // Alternative avec PATCH si votre API le supporte
content: Text('Amicale mise à jour avec succès'), // final response = await widget.apiService!.patch('/entites/${amicale.id}', data: data);
backgroundColor: Colors.green,
), debugPrint('📡 Réponse API: ${response.statusCode}');
);
if (response.statusCode == 200 || response.statusCode == 201) {
apiSuccess = true;
} else {
errorMessage = 'Erreur serveur: ${response.statusCode}';
} }
} catch (error) { } catch (error) {
// Afficher un message d'erreur debugPrint('❌ Erreur API: $error');
if (mounted) { errorMessage = 'Erreur lors de la communication avec le serveur: $error';
ScaffoldMessenger.of(context).showSnackBar(
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,
),
);
} }
} }
// Appeler la fonction onSubmit si elle existe // Fermer l'indicateur de chargement
if (widget.onSubmit != null) { if (mounted && Navigator.of(context).canPop()) {
widget.onSubmit!(amicale);
}
// Fermer le formulaire
if (mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
if (!mounted) return;
if (apiSuccess) {
// Appeler la fonction onSubmit si elle existe
if (widget.onSubmit != null) {
widget.onSubmit!(amicale);
}
// Afficher un message de succès
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(widget.apiService != null ? 'Amicale mise à jour avec succès' : 'Modifications enregistrées localement'),
backgroundColor: Colors.green,
),
);
// Fermer le formulaire après un délai pour que l'utilisateur voie le message
await Future.delayed(const Duration(milliseconds: 500));
if (mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
} else {
// Afficher un message d'erreur
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorMessage ?? 'Erreur lors de la mise à jour'),
backgroundColor: Colors.red,
duration: const Duration(seconds: 4),
),
);
}
} catch (e) { } catch (e) {
debugPrint('❌ Erreur générale dans _updateAmicale: $e');
// Fermer l'indicateur de chargement si encore ouvert // Fermer l'indicateur de chargement si encore ouvert
if (Navigator.of(context).canPop()) { if (mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
@@ -196,8 +223,9 @@ class _AmicaleFormState extends State<AmicaleForm> {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Erreur: ${e.toString()}'), content: Text('Erreur inattendue: ${e.toString()}'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
duration: const Duration(seconds: 4),
), ),
); );
} }
@@ -205,9 +233,14 @@ class _AmicaleFormState extends State<AmicaleForm> {
} }
void _submitForm() { void _submitForm() {
debugPrint('🔧 _submitForm appelée');
if (_formKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
debugPrint('🔧 Formulaire valide');
// Vérifier qu'au moins un numéro de téléphone est renseigné // Vérifier qu'au moins un numéro de téléphone est renseigné
if (_phoneController.text.isEmpty && _mobileController.text.isEmpty) { if (_phoneController.text.isEmpty && _mobileController.text.isEmpty) {
debugPrint('⚠️ Aucun numéro de téléphone renseigné');
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Veuillez renseigner au moins un numéro de téléphone'), content: Text('Veuillez renseigner au moins un numéro de téléphone'),
@@ -217,6 +250,8 @@ class _AmicaleFormState extends State<AmicaleForm> {
return; return;
} }
debugPrint('🔧 Création de l\'objet AmicaleModel...');
final amicale = widget.amicale?.copyWith( final amicale = widget.amicale?.copyWith(
name: _nameController.text, name: _nameController.text,
adresse1: _adresse1Controller.text, adresse1: _adresse1Controller.text,
@@ -258,10 +293,13 @@ class _AmicaleFormState extends State<AmicaleForm> {
chkActive: _chkActive, chkActive: _chkActive,
); );
debugPrint('🔧 AmicaleModel créé: ${amicale.name}');
debugPrint('🔧 Appel de _updateAmicale...');
// Appeler l'API pour mettre à jour l'amicale // Appeler l'API pour mettre à jour l'amicale
_updateAmicale(amicale); _updateAmicale(amicale);
} else {
// Ne pas appeler widget.onSubmit ici car c'est fait dans _updateAmicale debugPrint('❌ Formulaire invalide');
} }
} }

View File

@@ -22,7 +22,7 @@ class AmicaleTableWidget extends StatelessWidget {
final Function(AmicaleModel)? onDelete; final Function(AmicaleModel)? onDelete;
final AmicaleRepository amicaleRepository; final AmicaleRepository amicaleRepository;
final UserRepository userRepository; // Nouveau paramètre final UserRepository userRepository; // Nouveau paramètre
final ApiService? apiService; // Nouveau paramètre optionnel final ApiService? apiService;
final bool isLoading; final bool isLoading;
final String? emptyMessage; final String? emptyMessage;
final bool readOnly; final bool readOnly;
@@ -35,7 +35,7 @@ class AmicaleTableWidget extends StatelessWidget {
required this.userRepository, // Requis required this.userRepository, // Requis
this.onEdit, this.onEdit,
this.onDelete, this.onDelete,
this.apiService, // Optionnel this.apiService,
this.isLoading = false, this.isLoading = false,
this.emptyMessage, this.emptyMessage,
this.readOnly = false, this.readOnly = false,
@@ -83,9 +83,13 @@ class AmicaleTableWidget extends StatelessWidget {
readOnly: false, readOnly: false,
userRepository: userRepository, userRepository: userRepository,
apiService: apiService, apiService: apiService,
onSubmit: (updatedAmicale) { onSubmit: (updatedAmicale) async {
// Sauvegarder l'amicale mise à jour dans le repository
debugPrint('🔄 Sauvegarde de l\'amicale mise à jour: ${updatedAmicale.name}');
await amicaleRepository.saveAmicale(updatedAmicale);
debugPrint('✅ Amicale sauvegardée dans le repository');
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
// La mise à jour sera gérée par les ValueListenableBuilder
}, },
), ),
), ),
@@ -227,7 +231,12 @@ class AmicaleTableWidget extends StatelessWidget {
readOnly: true, readOnly: true,
userRepository: userRepository, userRepository: userRepository,
apiService: apiService, apiService: apiService,
onSubmit: (updatedAmicale) { onSubmit: (updatedAmicale) async {
// Sauvegarder l'amicale mise à jour dans le repository
debugPrint('🔄 Sauvegarde de l\'amicale mise à jour: ${updatedAmicale.name}');
await amicaleRepository.saveAmicale(updatedAmicale);
debugPrint('✅ Amicale sauvegardée dans le repository');
Navigator.of(dialogContext).pop(); Navigator.of(dialogContext).pop();
}, },
), ),

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/app.dart'; // Pour accéder à l'instance globale de ApiService import 'package:geosector_app/core/services/api_service.dart'; // Import du service singleton
/// Widget de carte réutilisable utilisant Mapbox /// Widget de carte réutilisable utilisant Mapbox
/// ///
@@ -105,11 +105,10 @@ class _MapboxMapState extends State<MapboxMap> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Déterminer l'URL du template de tuiles Mapbox // Déterminer l'URL du template de tuiles Mapbox
// Utiliser l'environnement actuel pour obtenir la bonne clé API // Utiliser l'environnement actuel pour obtenir la bonne clé API
final String environment = apiService.getCurrentEnvironment(); final String environment = ApiService.instance.getCurrentEnvironment();
final String mapboxToken = AppKeys.getMapboxApiKey(environment); final String mapboxToken = AppKeys.getMapboxApiKey(environment);
final String mapStyle = widget.mapStyle ?? 'mapbox/streets-v11'; final String mapStyle = widget.mapStyle ?? 'mapbox/streets-v11';
final String urlTemplate = final String urlTemplate = 'https://api.mapbox.com/styles/v1/$mapStyle/tiles/256/{z}/{x}/{y}@2x?access_token=$mapboxToken';
'https://api.mapbox.com/styles/v1/$mapStyle/tiles/256/{z}/{x}/{y}@2x?access_token=$mapboxToken';
return Stack( return Stack(
children: [ children: [
@@ -145,12 +144,10 @@ class _MapboxMapState extends State<MapboxMap> {
), ),
// Polygones // Polygones
if (widget.polygons != null && widget.polygons!.isNotEmpty) if (widget.polygons != null && widget.polygons!.isNotEmpty) PolygonLayer(polygons: widget.polygons!),
PolygonLayer(polygons: widget.polygons!),
// Marqueurs // Marqueurs
if (widget.markers != null && widget.markers!.isNotEmpty) if (widget.markers != null && widget.markers!.isNotEmpty) MarkerLayer(markers: widget.markers!),
MarkerLayer(markers: widget.markers!),
], ],
), ),

View File

@@ -44,7 +44,7 @@ class MembreRowWidget extends StatelessWidget {
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
membre.firstName, membre.firstName ?? '',
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -54,7 +54,7 @@ class MembreRowWidget extends StatelessWidget {
Expanded( Expanded(
flex: 2, flex: 2,
child: Text( child: Text(
membre.name, membre.name ?? '',
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -70,31 +70,31 @@ class MembreRowWidget extends StatelessWidget {
), ),
), ),
// Rôle (fkRole) // Rôle (role au lieu de fkRole)
Expanded( Expanded(
flex: 1, flex: 1,
child: Text( child: Text(
_getRoleName(membre.fkRole), _getRoleName(membre.role),
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
), ),
), ),
// Statut (actif/inactif) // Statut (isActive au lieu de chkActive)
Expanded( Expanded(
flex: 1, flex: 1,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1), color: membre.isActive ? Colors.green.withOpacity(0.1) : Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: membre.chkActive == 1 ? Colors.green.withOpacity(0.3) : Colors.red.withOpacity(0.3), color: membre.isActive ? Colors.green.withOpacity(0.3) : Colors.red.withOpacity(0.3),
), ),
), ),
child: Text( child: Text(
membre.chkActive == 1 ? 'Actif' : 'Inactif', membre.isActive ? 'Actif' : 'Inactif',
style: theme.textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: membre.chkActive == 1 ? Colors.green[700] : Colors.red[700], color: membre.isActive ? Colors.green[700] : Colors.red[700],
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -161,18 +161,20 @@ class MembreRowWidget extends StatelessWidget {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: Text('${membre.firstName} ${membre.name}'), title: Text('${membre.firstName ?? ''} ${membre.name ?? ''}'),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildDetailRow('ID', membre.id.toString()), _buildDetailRow('ID', membre.id.toString()),
_buildDetailRow('Email', membre.email), _buildDetailRow('Email', membre.email),
_buildDetailRow('Username', membre.username), _buildDetailRow('Username', membre.username ?? 'Non défini'),
_buildDetailRow('Rôle', _getRoleName(membre.fkRole)), _buildDetailRow('Rôle', _getRoleName(membre.role)),
_buildDetailRow('Titre', membre.fkTitre.toString()), _buildDetailRow('Titre', membre.fkTitre?.toString() ?? 'Non défini'),
_buildDetailRow('Secteur', membre.sectName ?? 'Non défini'), _buildDetailRow('Secteur', membre.sectName ?? 'Non défini'),
_buildDetailRow('Statut', membre.chkActive == 1 ? 'Actif' : 'Inactif'), _buildDetailRow('Statut', membre.isActive ? 'Actif' : 'Inactif'),
_buildDetailRow('Téléphone', membre.phone ?? 'Non défini'),
_buildDetailRow('Mobile', membre.mobile ?? 'Non défini'),
if (membre.dateNaissance != null) if (membre.dateNaissance != null)
_buildDetailRow('Date de naissance', '${membre.dateNaissance!.day}/${membre.dateNaissance!.month}/${membre.dateNaissance!.year}'), _buildDetailRow('Date de naissance', '${membre.dateNaissance!.day}/${membre.dateNaissance!.month}/${membre.dateNaissance!.year}'),
if (membre.dateEmbauche != null) if (membre.dateEmbauche != null)

View File

@@ -59,7 +59,7 @@ Hive.registerAdapter(SectorModelAdapter());
Hive.registerAdapter(PassageModelAdapter()); Hive.registerAdapter(PassageModelAdapter());
// Ouvrir les boîtes Hive // Ouvrir les boîtes Hive
await Hive.openBox<UserModel>(AppKeys.usersBoxName); await Hive.openBox<UserModel>(AppKeys.userBoxName);
await Hive.openBox<OperationModel>(AppKeys.operationsBoxName); await Hive.openBox<OperationModel>(AppKeys.operationsBoxName);
await Hive.openBox<SectorModel>(AppKeys.sectorsBoxName); await Hive.openBox<SectorModel>(AppKeys.sectorsBoxName);
await Hive.openBox<PassageModel>(AppKeys.passagesBoxName); await Hive.openBox<PassageModel>(AppKeys.passagesBoxName);
@@ -98,7 +98,7 @@ Hive.registerAdapter(SectorModelAdapter());
Hive.registerAdapter(PassageModelAdapter()); Hive.registerAdapter(PassageModelAdapter());
// N'ouvrir que la boîte des utilisateurs au démarrage // N'ouvrir que la boîte des utilisateurs au démarrage
await Hive.openBox<UserModel>(AppKeys.usersBoxName); await Hive.openBox<UserModel>(AppKeys.userBoxName);
await Hive.openBox(AppKeys.settingsBoxName); // Préférences générales await Hive.openBox(AppKeys.settingsBoxName); // Préférences générales
// Les autres boîtes seront ouvertes après connexion dans UserRepository.login() // Les autres boîtes seront ouvertes après connexion dans UserRepository.login()
@@ -228,8 +228,8 @@ Future<bool> logout() async {
Future<void> _deepCleanHiveBoxes() async { Future<void> _deepCleanHiveBoxes() async {
try { try {
// 1. Vider toutes les boîtes sans les fermer // 1. Vider toutes les boîtes sans les fermer
if (Hive.isBoxOpen(AppKeys.usersBoxName)) { if (Hive.isBoxOpen(AppKeys.userBoxName)) {
await Hive.box<UserModel>(AppKeys.usersBoxName).clear(); await Hive.box<UserModel>(AppKeys.userBoxName).clear();
} }
if (Hive.isBoxOpen(AppKeys.operationsBoxName)) { if (Hive.isBoxOpen(AppKeys.operationsBoxName)) {
@@ -336,7 +336,7 @@ Future<void> _ensureBoxIsOpen(String boxName) async {
await Hive.openBox<OperationModel>(boxName); await Hive.openBox<OperationModel>(boxName);
} else if (boxName == AppKeys.sectorsBoxName) { } else if (boxName == AppKeys.sectorsBoxName) {
await Hive.openBox<SectorModel>(boxName); await Hive.openBox<SectorModel>(boxName);
} else if (boxName == AppKeys.usersBoxName) { } else if (boxName == AppKeys.userBoxName) {
await Hive.openBox<UserModel>(boxName); await Hive.openBox<UserModel>(boxName);
} else { } else {
await Hive.openBox(boxName); await Hive.openBox(boxName);