On release/v3.1.4: Sauvegarde temporaire pour changement de branche

This commit is contained in:
2025-08-21 17:51:22 +02:00
parent d275d0ab0c
commit f5bef999df
64 changed files with 85343 additions and 83615 deletions

View File

@@ -8,7 +8,8 @@
"ext-openssl": "*",
"ext-pdo": "*",
"phpmailer/phpmailer": "^6.8",
"phpoffice/phpspreadsheet": "^2.0"
"phpoffice/phpspreadsheet": "^2.0",
"setasign/fpdf": "^1.8"
},
"autoload": {
"classmap": [

View File

@@ -141,6 +141,11 @@ $SSH_JUMP_CMD "
incus exec ${INCUS_CONTAINER} -- chmod -R 775 ${FINAL_PATH}/uploads || exit 1
incus exec ${INCUS_CONTAINER} -- find ${FINAL_PATH}/uploads -type f -exec chmod -R 664 {} \; || exit 1
echo '📦 Mise à jour des dépendances Composer...'
incus exec ${INCUS_CONTAINER} -- bash -c 'cd ${FINAL_PATH} && composer update --no-dev --optimize-autoloader' || {
echo '⚠️ Composer non disponible ou échec, poursuite sans mise à jour des dépendances'
}
echo '🧹 Nettoyage...'
incus exec ${INCUS_CONTAINER} -- rm -f /tmp/${ARCHIVE_NAME} || exit 1
rm -f /tmp/${ARCHIVE_NAME} || exit 1

View File

@@ -142,6 +142,15 @@ fi
echo "✅ Propriétaire et permissions appliqués avec succès"
# Mise à jour des dépendances Composer
echo "📦 Mise à jour des dépendances Composer sur $DEST_CONTAINER..."
ssh -i $HOST_KEY -p $HOST_PORT $HOST_USER@$HOST_IP "incus exec $DEST_CONTAINER --project $PROJECT -- bash -c 'cd $API_PATH && composer update --no-dev --optimize-autoloader'" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "✅ Dépendances Composer mises à jour avec succès"
else
echo "⚠️ Composer non disponible ou échec, poursuite sans mise à jour des dépendances"
fi
# Vérifier la copie
echo "✅ Vérification de la copie..."
ssh -i $HOST_KEY -p $HOST_PORT $HOST_USER@$HOST_IP "incus exec $DEST_CONTAINER --project $PROJECT -- test -d $API_PATH"

View File

@@ -57,8 +57,9 @@ class EntiteController {
ville,
fk_type,
created_at,
chk_active
) VALUES (?, ?, ?, 1, NOW(), 1)
chk_active,
chk_user_delete_pass
) VALUES (?, ?, ?, 1, NOW(), 1, 0)
');
$stmt->execute([
@@ -109,7 +110,7 @@ class EntiteController {
public function getEntiteById(int $id): array|false {
try {
$stmt = $this->db->prepare('
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active, chk_user_delete_pass
FROM entites
WHERE id = ? AND chk_active = 1
');
@@ -146,7 +147,7 @@ class EntiteController {
public function getEntiteByPostalCode(string $postalCode): array|false {
try {
$stmt = $this->db->prepare('
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active, chk_user_delete_pass
FROM entites
WHERE code_postal = ? AND chk_active = 1
');
@@ -247,7 +248,7 @@ class EntiteController {
public function getEntites(): void {
try {
$stmt = $this->db->prepare('
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active
SELECT id, encrypted_name, code_postal, ville, fk_type, chk_active, chk_user_delete_pass
FROM entites
WHERE chk_active = 1
ORDER BY code_postal ASC
@@ -587,6 +588,11 @@ class EntiteController {
$updateFields[] = 'chk_username_manuel = ?';
$params[] = $data['chk_username_manuel'] ? 1 : 0;
}
if (isset($data['chk_user_delete_pass'])) {
$updateFields[] = 'chk_user_delete_pass = ?';
$params[] = $data['chk_user_delete_pass'] ? 1 : 0;
}
}
// Si aucun champ à mettre à jour, retourner une erreur
@@ -728,7 +734,7 @@ class EntiteController {
// Créer le dossier de destination
require_once __DIR__ . '/../Services/FileService.php';
$fileService = new \FileService();
$uploadPath = "/entites/{$entiteId}/logo";
$uploadPath = "/{$entiteId}/logo";
$fullPath = $fileService->createDirectory($entiteId, $uploadPath);
// Nom du fichier final

View File

@@ -533,7 +533,7 @@ class LoginController {
e.fk_region, r.libelle AS lib_region, e.fk_type, e.encrypted_phone as phone, e.encrypted_mobile as mobile,
e.encrypted_email as email, e.gps_lat, e.gps_lng,
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_mdp_manuel, e.chk_username_manuel,
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe, e.chk_user_delete_pass
FROM entites e
LEFT JOIN x_regions r ON e.fk_region = r.id
WHERE e.id = ? AND e.chk_active = 1'
@@ -547,7 +547,7 @@ class LoginController {
e.fk_region, r.libelle AS lib_region, e.fk_type, e.encrypted_phone as phone, e.encrypted_mobile as mobile,
e.encrypted_email as email, e.gps_lat, e.gps_lng,
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_mdp_manuel, e.chk_username_manuel,
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe, e.chk_user_delete_pass
FROM entites e
LEFT JOIN x_regions r ON e.fk_region = r.id
WHERE e.id != 1 AND e.chk_active = 1'
@@ -600,7 +600,7 @@ class LoginController {
e.fk_region, r.libelle AS lib_region, e.fk_type, e.encrypted_phone as phone, e.encrypted_mobile as mobile,
e.encrypted_email as email, e.gps_lat, e.gps_lng,
e.encrypted_stripe_id as stripe_id, e.chk_demo, e.chk_mdp_manuel, e.chk_username_manuel,
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe
e.chk_copie_mail_recu, e.chk_accept_sms, e.chk_active, e.chk_stripe, e.chk_user_delete_pass
FROM entites e
LEFT JOIN x_regions r ON e.fk_region = r.id
WHERE e.fk_type = 1 AND e.chk_active = 1'

View File

@@ -552,8 +552,26 @@ class PassageController {
'operationId' => $operationId
]);
// Envoyer la réponse immédiatement pour éviter les timeouts
Response::json([
'status' => 'success',
'message' => 'Passage créé avec succès',
'passage_id' => $passageId,
'receipt_generated' => false // On va générer le reçu en arrière-plan
], 201);
// Flush la sortie pour s'assurer que la réponse est envoyée
if (ob_get_level()) {
ob_end_flush();
}
flush();
// Fermer la connexion HTTP mais continuer le traitement
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
// Générer automatiquement un reçu si c'est un don (fk_type = 1) avec email valide
$receiptGenerated = false;
if (isset($data['fk_type']) && (int)$data['fk_type'] === 1) {
// Vérifier si un email a été fourni
$hasEmail = false;
@@ -584,13 +602,8 @@ class PassageController {
}
}
}
Response::json([
'status' => 'success',
'message' => 'Passage créé avec succès',
'passage_id' => $passageId,
'receipt_generated' => $receiptGenerated
], 201);
return; // Fin de la méthode, éviter d'exécuter le code après
} catch (Exception $e) {
LogService::log('Erreur lors de la création du passage', [
'level' => 'error',
@@ -740,25 +753,41 @@ class PassageController {
'passageId' => $passageId
]);
// Vérifier si un reçu doit être généré après la mise à jour
$receiptGenerated = false;
// Envoyer la réponse immédiatement pour éviter les timeouts
Response::json([
'status' => 'success',
'message' => 'Passage mis à jour avec succès',
'receipt_generated' => false // On va générer le reçu en arrière-plan
], 200);
// Récupérer les données actualisées du passage
$stmt = $this->db->prepare('SELECT fk_type, encrypted_email, nom_recu FROM ope_pass WHERE id = ?');
$stmt->execute([$passageId]);
$updatedPassage = $stmt->fetch(PDO::FETCH_ASSOC);
// Flush la sortie pour s'assurer que la réponse est envoyée
if (ob_get_level()) {
ob_end_flush();
}
flush();
if ($updatedPassage) {
// Générer un reçu si :
// - C'est un don (fk_type = 1)
// - Il y a un email valide
// - Il n'y a pas encore de reçu (nom_recu est vide ou null)
if ((int)$updatedPassage['fk_type'] === 1 &&
!empty($updatedPassage['encrypted_email']) &&
empty($updatedPassage['nom_recu'])) {
// Vérifier que l'email est valide en le déchiffrant
try {
// Fermer la connexion HTTP mais continuer le traitement
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
// Maintenant générer le reçu en arrière-plan après avoir envoyé la réponse
try {
// Récupérer les données actualisées du passage
$stmt = $this->db->prepare('SELECT fk_type, encrypted_email, nom_recu FROM ope_pass WHERE id = ?');
$stmt->execute([$passageId]);
$updatedPassage = $stmt->fetch(PDO::FETCH_ASSOC);
if ($updatedPassage) {
// Générer un reçu si :
// - C'est un don (fk_type = 1)
// - Il y a un email valide
// - Il n'y a pas encore de reçu (nom_recu est vide ou null)
if ((int)$updatedPassage['fk_type'] === 1 &&
!empty($updatedPassage['encrypted_email']) &&
empty($updatedPassage['nom_recu'])) {
// Vérifier que l'email est valide en le déchiffrant
$email = ApiService::decryptSearchableData($updatedPassage['encrypted_email']);
if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
@@ -772,21 +801,17 @@ class PassageController {
]);
}
}
} catch (Exception $e) {
LogService::log('Erreur lors de la génération automatique du reçu après mise à jour', [
'level' => 'warning',
'error' => $e->getMessage(),
'passageId' => $passageId
]);
}
}
} catch (Exception $e) {
LogService::log('Erreur lors de la génération automatique du reçu après mise à jour', [
'level' => 'warning',
'error' => $e->getMessage(),
'passageId' => $passageId
]);
}
Response::json([
'status' => 'success',
'message' => 'Passage mis à jour avec succès',
'receipt_generated' => $receiptGenerated
], 200);
return; // Fin de la méthode, éviter d'exécuter le code après
} catch (Exception $e) {
LogService::log('Erreur lors de la mise à jour du passage', [
'level' => 'error',
@@ -818,8 +843,47 @@ class PassageController {
$passageId = (int)$id;
// Récupérer le rôle de l'utilisateur
$stmt = $this->db->prepare('SELECT fk_role, fk_entite FROM users WHERE id = ?');
$stmt->execute([$userId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur non trouvé'
], 404);
return;
}
$userRole = (int)$user['fk_role'];
$entiteId = (int)$user['fk_entite'];
// Si l'utilisateur est un membre (fk_role = 1), vérifier les permissions de l'entité
if ($userRole === 1) {
$stmt = $this->db->prepare('SELECT chk_user_delete_pass FROM entites WHERE id = ?');
$stmt->execute([$entiteId]);
$entite = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$entite || (int)$entite['chk_user_delete_pass'] !== 1) {
LogService::log('Tentative de suppression de passage non autorisée', [
'level' => 'warning',
'userId' => $userId,
'userRole' => $userRole,
'entiteId' => $entiteId,
'passageId' => $passageId,
'chk_user_delete_pass' => $entite ? $entite['chk_user_delete_pass'] : null
]);
Response::json([
'status' => 'error',
'message' => 'Vous n\'avez pas l\'autorisation de supprimer des passages'
], 403);
return;
}
}
// Vérifier que le passage existe et appartient à l'entité de l'utilisateur
$entiteId = $this->getUserEntiteId($userId);
if (!$entiteId) {
Response::json([
'status' => 'error',

View File

@@ -72,12 +72,17 @@ class MonitoredDatabase extends PDO {
/**
* Query avec monitoring
*/
public function query($statement, $mode = PDO::ATTR_DEFAULT_FETCH_MODE, ...$args): PDOStatement|false {
public function query($statement, $mode = null, ...$args): PDOStatement|false {
// Démarrer le chronométrage
PerformanceMonitor::startDbQuery($statement);
try {
$result = parent::query($statement, $mode, ...$args);
// Si pas de mode spécifié, utiliser query simple
if ($mode === null) {
$result = parent::query($statement);
} else {
$result = parent::query($statement, $mode, ...$args);
}
// Terminer le chronométrage
PerformanceMonitor::endDbQuery();

View File

@@ -37,7 +37,7 @@ class ExportService {
}
// Créer le dossier de destination
$exportDir = $this->fileService->createDirectory($entiteId, "/{$entiteId}/operations/{$operationId}/exports/excel");
$exportDir = $this->fileService->createDirectory($entiteId, "/{$entiteId}/operations/{$operationId}");
LogService::log('exportDir', [
'level' => 'warning',
@@ -138,7 +138,7 @@ class ExportService {
$exportData = $this->collectOperationData($operationId, $entiteId);
// Créer le dossier de destination
$exportDir = $this->fileService->createDirectory($entiteId, "/{$entiteId}/operations/{$operationId}/exports/json");
$exportDir = $this->fileService->createDirectory($entiteId, "/{$entiteId}/operations/{$operationId}");
// Initialiser le service de chiffrement
$backupService = new BackupEncryptionService();

View File

@@ -7,6 +7,7 @@ namespace App\Services;
require_once __DIR__ . '/LogService.php';
require_once __DIR__ . '/ApiService.php';
require_once __DIR__ . '/FileService.php';
require_once __DIR__ . '/ReceiptPDFGenerator.php';
use PDO;
use Database;
@@ -24,6 +25,8 @@ class ReceiptService {
private PDO $db;
private FileService $fileService;
private const DEFAULT_LOGO_PATH = __DIR__ . '/../../docs/_logo_recu.png';
private const LOGO_WIDTH = 40; // Largeur du logo en mm (80 est trop grand pour un A4)
private const LOGO_HEIGHT = 40; // Hauteur du logo en mm
public function __construct() {
$this->db = Database::getInstance();
@@ -89,7 +92,7 @@ class ReceiptService {
$pdfContent = $this->generateOptimizedPDF($receiptData, $logoPath);
// Créer le répertoire de stockage
$uploadPath = "/entites/{$operationData['fk_entite']}/recus/{$operationData['id']}";
$uploadPath = "/{$operationData['fk_entite']}/recus/{$operationData['id']}";
$fullPath = $this->fileService->createDirectory($operationData['fk_entite'], $uploadPath);
// Nom du fichier
@@ -141,181 +144,13 @@ class ReceiptService {
}
/**
* Génère un PDF ultra-optimisé (< 20KB)
* Utilise le format PDF natif pour minimiser la taille
* Génère un PDF optimisé avec logo et mise en page épurée
*/
private function generateOptimizedPDF(array $data, ?string $logoPath): string {
// Début du PDF
$pdf = "%PDF-1.3\n";
$objects = [];
$xref = [];
// Object 1 - Catalog
$objects[1] = "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
// Object 2 - Pages
$objects[2] = "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n";
// Object 3 - Page
$objects[3] = "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>\nendobj\n";
// Object 4 - Font (Helvetica)
$objects[4] = "4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n";
// Contenu de la page (texte du reçu)
$content = $this->generatePDFContent($data);
// Object 5 - Content stream
$contentLength = strlen($content);
$objects[5] = "5 0 obj\n<< /Length $contentLength >>\nstream\n$content\nendstream\nendobj\n";
// Construction du PDF final
$offset = strlen($pdf);
foreach ($objects as $obj) {
$xref[] = $offset;
$pdf .= $obj;
$offset += strlen($obj);
}
// Table xref
$pdf .= "xref\n";
$pdf .= "0 " . (count($objects) + 1) . "\n";
$pdf .= "0000000000 65535 f \n";
foreach ($xref as $off) {
$pdf .= sprintf("%010d 00000 n \n", $off);
}
// Trailer
$pdf .= "trailer\n";
$pdf .= "<< /Size " . (count($objects) + 1) . " /Root 1 0 R >>\n";
$pdf .= "startxref\n";
$pdf .= "$offset\n";
$pdf .= "%%EOF\n";
return $pdf;
$pdf = new ReceiptPDFGenerator();
return $pdf->generateReceipt($data, $logoPath);
}
/**
* Génère le contenu textuel du reçu pour le PDF
*/
private function generatePDFContent(array $data): string {
$content = "BT\n";
$content .= "/F1 12 Tf\n";
$y = 750;
// En-tête
$content .= "50 $y Td\n";
$content .= "(" . $this->escapeString($data['entite_name']) . ") Tj\n";
$y -= 20;
if (!empty($data['entite_address'])) {
$content .= "0 -20 Td\n";
$content .= "(" . $this->escapeString($data['entite_address']) . ") Tj\n";
$y -= 20;
}
// Titre du reçu
$y -= 40;
$content .= "/F1 16 Tf\n";
$content .= "0 -40 Td\n";
$content .= "(RECU DE DON N° " . $data['receipt_number'] . ") Tj\n";
$content .= "/F1 10 Tf\n";
$content .= "0 -15 Td\n";
$content .= "(Article 200 du Code General des Impots) Tj\n";
// Informations du donateur
$y -= 60;
$content .= "/F1 12 Tf\n";
$content .= "0 -45 Td\n";
$content .= "(DONATEUR) Tj\n";
$content .= "/F1 11 Tf\n";
$content .= "0 -20 Td\n";
$content .= "(Nom : " . $this->escapeString($data['donor_name']) . ") Tj\n";
if (!empty($data['donor_address'])) {
$content .= "0 -15 Td\n";
$content .= "(Adresse : " . $this->escapeString($data['donor_address']) . ") Tj\n";
}
if (!empty($data['donor_email'])) {
$content .= "0 -15 Td\n";
$content .= "(Email : " . $this->escapeString($data['donor_email']) . ") Tj\n";
}
// Détails du don
$content .= "0 -30 Td\n";
$content .= "/F1 12 Tf\n";
$content .= "(DETAILS DU DON) Tj\n";
$content .= "/F1 11 Tf\n";
$content .= "0 -20 Td\n";
$content .= "(Date : " . $data['donation_date'] . ") Tj\n";
$content .= "0 -15 Td\n";
$content .= "(Montant : " . $data['amount'] . " EUR) Tj\n";
$content .= "0 -15 Td\n";
$content .= "(Mode de reglement : " . $this->escapeString($data['payment_method']) . ") Tj\n";
if (!empty($data['operation_name'])) {
$content .= "0 -15 Td\n";
$content .= "(Campagne : " . $this->escapeString($data['operation_name']) . ") Tj\n";
}
// Mention légale
$content .= "/F1 9 Tf\n";
$content .= "0 -40 Td\n";
$content .= "(Reduction d'impot egale a 66% du montant verse dans la limite de 20% du revenu imposable) Tj\n";
// Date et signature
$content .= "/F1 11 Tf\n";
$content .= "0 -30 Td\n";
$content .= "(Fait a " . $this->escapeString($data['entite_city']) . ", le " . $data['signature_date'] . ") Tj\n";
$content .= "0 -20 Td\n";
$content .= "(Le President) Tj\n";
$content .= "ET\n";
return $content;
}
/**
* Échappe les caractères spéciaux pour le PDF
*/
private function escapeString(string $str): string {
// Échapper les caractères spéciaux PDF
$str = str_replace('\\', '\\\\', $str);
$str = str_replace('(', '\\(', $str);
$str = str_replace(')', '\\)', $str);
// Remplacer manuellement les caractères accentués les plus courants
$accents = [
'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A',
'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a',
'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E',
'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O',
'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o',
'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U',
'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u',
'Ñ' => 'N', 'ñ' => 'n',
'Ç' => 'C', 'ç' => 'c',
'Œ' => 'OE', 'œ' => 'oe',
'Æ' => 'AE', 'æ' => 'ae'
];
$str = strtr($str, $accents);
// Supprimer tout caractère non-ASCII restant
$str = preg_replace('/[^\x20-\x7E]/', '', $str);
return $str;
}
/**
* Récupère les données du passage

File diff suppressed because one or more lines are too long

View File

@@ -38,13 +38,14 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
chkMdpManuel: fields[22] as bool,
chkUsernameManuel: fields[23] as bool,
logoBase64: fields[24] as String?,
chkUserDeletePass: fields[25] as bool,
);
}
@override
void write(BinaryWriter writer, AmicaleModel obj) {
writer
..writeByte(25)
..writeByte(26)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -94,7 +95,9 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
..writeByte(23)
..write(obj.chkUsernameManuel)
..writeByte(24)
..write(obj.logoBase64);
..write(obj.logoBase64)
..writeByte(25)
..write(obj.chkUserDeletePass);
}
@override

View File

@@ -37,13 +37,14 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
updatedAt: fields[21] as DateTime?,
chkMdpManuel: fields[22] as bool?,
chkUsernameManuel: fields[23] as bool?,
chkUserDeletePass: fields[24] as bool?,
);
}
@override
void write(BinaryWriter writer, ClientModel obj) {
writer
..writeByte(24)
..writeByte(25)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -91,7 +92,9 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
..writeByte(22)
..write(obj.chkMdpManuel)
..writeByte(23)
..write(obj.chkUsernameManuel);
..write(obj.chkUsernameManuel)
..writeByte(24)
..write(obj.chkUserDeletePass);
}
@override

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/dart_build_result.json:

View File

@@ -1 +0,0 @@
{"inputs":["/home/pierre/dev/flutter/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart","/home/pierre/dev/geosector/app/.dart_tool/package_config_subset"],"outputs":["/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/dart_build_result.json","/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/dart_build_result.json"]}

View File

@@ -1 +0,0 @@
{"dependencies":[],"code_assets":[]}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"inputs":["/home/pierre/dev/geosector/app/.dart_tool/package_config_subset"],"outputs":["/home/pierre/dev/geosector/app/.dart_tool/flutter_build/dart_plugin_registrant.dart"]}

View File

@@ -1 +0,0 @@
/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/native_assets.json:

View File

@@ -1 +0,0 @@
{"inputs":["/home/pierre/dev/flutter/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart","/home/pierre/dev/geosector/app/.dart_tool/package_config_subset"],"outputs":["/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/native_assets.json","/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/native_assets.json"]}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"format-version":[1,0,0],"native-assets":{}}

View File

@@ -1 +0,0 @@
["/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/vm_snapshot_data","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/isolate_snapshot_data","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/kernel_blob.bin","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/images/logo-geosector-512.png-autosave.kra","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/images/icon-geosector.svg","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/images/geosector_map_admin.png","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/images/logo_recu.png","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/images/logo-geosector-512.png","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/images/geosector-logo.png","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/images/logo-geosector-1024.png","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/animations/geo_main.json","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/lib/chat/chat_config.yaml","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/assets/fonts/Figtree-VariableFont_wght.ttf","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/packages/flutter_map/lib/assets/flutter_map_logo.png","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/fonts/MaterialIcons-Regular.otf","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/shaders/ink_sparkle.frag","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/AssetManifest.json","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/AssetManifest.bin","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/FontManifest.json","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/NOTICES.Z","/home/pierre/dev/geosector/app/build/app/intermediates/flutter/debug/flutter_assets/NativeAssetsManifest.json"]

File diff suppressed because one or more lines are too long

View File

@@ -787,101 +787,101 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/span_
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/string_scanner.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/utils.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/string_scanner.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/charts.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/datetime_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/datetime_category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/logarithmic_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/multi_level_labels.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/numeric_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/plot_band.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/base.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/behaviors/crosshair.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/behaviors/trackball.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/behaviors/zooming.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/cartesian_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/circular_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/annotation.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/callbacks.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/chart_point.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/circular_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/circular_data_label_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/connector_line.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/core_legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/core_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/element_widget.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/empty_points.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/funnel_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/interactive_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/layout_handler.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/pyramid_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/title.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/funnel_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/accumulation_distribution_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/atr_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/bollinger_bands_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/ema_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/macd_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/momentum_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/roc_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/rsi_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/sma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/stochastic_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/technical_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/tma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/wma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/interactions/behavior.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/interactions/selection.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/interactions/tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/pyramid_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/box_and_whisker_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/bubble_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/candle_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/chart_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/doughnut_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/error_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/fast_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/funnel_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/hilo_open_close_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/hilo_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/histogram_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/pie_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/pyramid_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/radial_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/range_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/range_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/scatter_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/spline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_area100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_bar100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_column100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_line100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/step_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stepline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/waterfall_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/theme.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/trendline/trendline.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/constants.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/renderer_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/typedef.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/zooming_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/sparkline/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/sparkline/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/sparkline/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/charts.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/datetime_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/datetime_category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/logarithmic_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/multi_level_labels.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/numeric_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/plot_band.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/base.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/behaviors/crosshair.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/behaviors/trackball.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/behaviors/zooming.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/cartesian_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/circular_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/annotation.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/callbacks.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/chart_point.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/circular_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/circular_data_label_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/connector_line.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/core_legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/core_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/element_widget.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/empty_points.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/funnel_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/interactive_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/layout_handler.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/pyramid_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/title.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/funnel_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/accumulation_distribution_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/atr_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/bollinger_bands_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/ema_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/macd_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/momentum_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/roc_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/rsi_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/sma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/stochastic_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/technical_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/tma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/wma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/interactions/behavior.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/interactions/selection.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/interactions/tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/pyramid_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/box_and_whisker_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/bubble_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/candle_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/chart_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/doughnut_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/error_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/fast_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/funnel_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/hilo_open_close_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/hilo_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/histogram_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/pie_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/pyramid_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/radial_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/range_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/range_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/scatter_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/spline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_area100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_bar100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_column100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_line100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/step_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stepline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/waterfall_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/theme.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/trendline/trendline.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/constants.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/renderer_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/typedef.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/zooming_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/sparkline/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/sparkline/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/sparkline/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.6/lib/core.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.6/lib/localizations.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.6/lib/src/calendar/calendar_helper.dart
@@ -1982,7 +1982,7 @@ file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_table_widget.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/operation_form_dialog.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passage_form_dialog.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passages/passage_form.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passage_map_dialog.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passages/passages_list_widget.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/responsive_navigation.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/sector_distribution_card.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

@@ -787,101 +787,101 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/span_
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/string_scanner.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/src/utils.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/string_scanner.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/charts.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/datetime_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/datetime_category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/logarithmic_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/multi_level_labels.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/numeric_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/axis/plot_band.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/base.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/behaviors/crosshair.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/behaviors/trackball.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/behaviors/zooming.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/cartesian_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/circular_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/annotation.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/callbacks.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/chart_point.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/circular_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/circular_data_label_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/connector_line.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/core_legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/core_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/element_widget.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/empty_points.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/funnel_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/interactive_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/layout_handler.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/pyramid_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/common/title.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/funnel_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/accumulation_distribution_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/atr_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/bollinger_bands_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/ema_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/macd_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/momentum_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/roc_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/rsi_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/sma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/stochastic_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/technical_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/tma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/indicators/wma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/interactions/behavior.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/interactions/selection.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/interactions/tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/pyramid_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/box_and_whisker_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/bubble_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/candle_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/chart_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/doughnut_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/error_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/fast_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/funnel_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/hilo_open_close_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/hilo_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/histogram_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/pie_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/pyramid_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/radial_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/range_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/range_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/scatter_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/spline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_area100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_bar100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_column100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_line100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stacked_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/step_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/stepline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/series/waterfall_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/theme.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/trendline/trendline.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/constants.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/renderer_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/typedef.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/charts/utils/zooming_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/sparkline/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/sparkline/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/src/sparkline/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/charts.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/datetime_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/datetime_category_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/logarithmic_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/multi_level_labels.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/numeric_axis.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/axis/plot_band.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/base.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/behaviors/crosshair.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/behaviors/trackball.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/behaviors/zooming.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/cartesian_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/circular_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/annotation.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/callbacks.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/chart_point.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/circular_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/circular_data_label_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/connector_line.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/core_legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/core_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/element_widget.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/empty_points.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/funnel_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/interactive_tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/layout_handler.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/legend.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/pyramid_data_label.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/common/title.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/funnel_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/accumulation_distribution_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/atr_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/bollinger_bands_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/ema_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/macd_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/momentum_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/roc_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/rsi_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/sma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/stochastic_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/technical_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/tma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/indicators/wma_indicator.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/interactions/behavior.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/interactions/selection.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/interactions/tooltip.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/pyramid_chart.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/box_and_whisker_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/bubble_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/candle_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/chart_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/doughnut_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/error_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/fast_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/funnel_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/hilo_open_close_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/hilo_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/histogram_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/pie_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/pyramid_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/radial_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/range_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/range_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/scatter_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/spline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_area100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_bar100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_bar_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_column100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_column_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_line100_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stacked_line_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/step_area_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/stepline_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/series/waterfall_series.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/theme.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/trendline/trendline.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/constants.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/renderer_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/typedef.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/charts/utils/zooming_helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/sparkline/marker.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/sparkline/utils/enum.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/src/sparkline/utils/helper.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.6/lib/core.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.6/lib/localizations.dart
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.6/lib/src/calendar/calendar_helper.dart
@@ -1981,7 +1981,7 @@ file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_row_widget
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/membre_table_widget.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/operation_form_dialog.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passage_form_dialog.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passages/passage_form.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passage_map_dialog.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/passages/passages_list_widget.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/responsive_navigation.dart
file:///home/pierre/dev/geosector/app/lib/presentation/widgets/sector_distribution_card.dart

File diff suppressed because one or more lines are too long

View File

@@ -1,372 +0,0 @@
//
// Generated file. Do not edit.
// This file is generated from template in file `flutter_tools/lib/src/flutter_plugins.dart`.
//
// @dart = 3.0
import 'dart:io'; // flutter_ignore: dart_io_import.
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:geolocator_android/geolocator_android.dart';
import 'package:image_picker_android/image_picker_android.dart';
import 'package:path_provider_android/path_provider_android.dart';
import 'package:shared_preferences_android/shared_preferences_android.dart';
import 'package:url_launcher_android/url_launcher_android.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:geolocator_apple/geolocator_apple.dart';
import 'package:image_picker_ios/image_picker_ios.dart';
import 'package:path_provider_foundation/path_provider_foundation.dart';
import 'package:shared_preferences_foundation/shared_preferences_foundation.dart';
import 'package:url_launcher_ios/url_launcher_ios.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:file_selector_linux/file_selector_linux.dart';
import 'package:flutter_local_notifications_linux/flutter_local_notifications_linux.dart';
import 'package:geolocator_linux/geolocator_linux.dart';
import 'package:image_picker_linux/image_picker_linux.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider_linux/path_provider_linux.dart';
import 'package:shared_preferences_linux/shared_preferences_linux.dart';
import 'package:url_launcher_linux/url_launcher_linux.dart';
import 'package:file_selector_macos/file_selector_macos.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:geolocator_apple/geolocator_apple.dart';
import 'package:image_picker_macos/image_picker_macos.dart';
import 'package:path_provider_foundation/path_provider_foundation.dart';
import 'package:shared_preferences_foundation/shared_preferences_foundation.dart';
import 'package:url_launcher_macos/url_launcher_macos.dart';
import 'package:file_selector_windows/file_selector_windows.dart';
import 'package:flutter_local_notifications_windows/flutter_local_notifications_windows.dart';
import 'package:image_picker_windows/image_picker_windows.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider_windows/path_provider_windows.dart';
import 'package:shared_preferences_windows/shared_preferences_windows.dart';
import 'package:url_launcher_windows/url_launcher_windows.dart';
@pragma('vm:entry-point')
class _PluginRegistrant {
@pragma('vm:entry-point')
static void register() {
if (Platform.isAndroid) {
try {
AndroidFlutterLocalNotificationsPlugin.registerWith();
} catch (err) {
print(
'`flutter_local_notifications` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
GeolocatorAndroid.registerWith();
} catch (err) {
print(
'`geolocator_android` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
ImagePickerAndroid.registerWith();
} catch (err) {
print(
'`image_picker_android` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderAndroid.registerWith();
} catch (err) {
print(
'`path_provider_android` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
SharedPreferencesAndroid.registerWith();
} catch (err) {
print(
'`shared_preferences_android` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
UrlLauncherAndroid.registerWith();
} catch (err) {
print(
'`url_launcher_android` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isIOS) {
try {
IOSFlutterLocalNotificationsPlugin.registerWith();
} catch (err) {
print(
'`flutter_local_notifications` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
GeolocatorApple.registerWith();
} catch (err) {
print(
'`geolocator_apple` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
ImagePickerIOS.registerWith();
} catch (err) {
print(
'`image_picker_ios` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderFoundation.registerWith();
} catch (err) {
print(
'`path_provider_foundation` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
SharedPreferencesFoundation.registerWith();
} catch (err) {
print(
'`shared_preferences_foundation` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
UrlLauncherIOS.registerWith();
} catch (err) {
print(
'`url_launcher_ios` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isLinux) {
try {
ConnectivityPlusLinuxPlugin.registerWith();
} catch (err) {
print(
'`connectivity_plus` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
FileSelectorLinux.registerWith();
} catch (err) {
print(
'`file_selector_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
LinuxFlutterLocalNotificationsPlugin.registerWith();
} catch (err) {
print(
'`flutter_local_notifications_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
GeolocatorLinux.registerWith();
} catch (err) {
print(
'`geolocator_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
ImagePickerLinux.registerWith();
} catch (err) {
print(
'`image_picker_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PackageInfoPlusLinuxPlugin.registerWith();
} catch (err) {
print(
'`package_info_plus` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderLinux.registerWith();
} catch (err) {
print(
'`path_provider_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
SharedPreferencesLinux.registerWith();
} catch (err) {
print(
'`shared_preferences_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
UrlLauncherLinux.registerWith();
} catch (err) {
print(
'`url_launcher_linux` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isMacOS) {
try {
FileSelectorMacOS.registerWith();
} catch (err) {
print(
'`file_selector_macos` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
MacOSFlutterLocalNotificationsPlugin.registerWith();
} catch (err) {
print(
'`flutter_local_notifications` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
GeolocatorApple.registerWith();
} catch (err) {
print(
'`geolocator_apple` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
ImagePickerMacOS.registerWith();
} catch (err) {
print(
'`image_picker_macos` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderFoundation.registerWith();
} catch (err) {
print(
'`path_provider_foundation` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
SharedPreferencesFoundation.registerWith();
} catch (err) {
print(
'`shared_preferences_foundation` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
UrlLauncherMacOS.registerWith();
} catch (err) {
print(
'`url_launcher_macos` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
} else if (Platform.isWindows) {
try {
FileSelectorWindows.registerWith();
} catch (err) {
print(
'`file_selector_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
FlutterLocalNotificationsWindows.registerWith();
} catch (err) {
print(
'`flutter_local_notifications_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
ImagePickerWindows.registerWith();
} catch (err) {
print(
'`image_picker_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PackageInfoPlusWindowsPlugin.registerWith();
} catch (err) {
print(
'`package_info_plus` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
PathProviderWindows.registerWith();
} catch (err) {
print(
'`path_provider_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
SharedPreferencesWindows.registerWith();
} catch (err) {
print(
'`shared_preferences_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
try {
UrlLauncherWindows.registerWith();
} catch (err) {
print(
'`url_launcher_windows` threw an error: $err. '
'The app may not function as expected until you remove this plugin from pubspec.yaml'
);
}
}
}
}

View File

@@ -909,7 +909,7 @@
},
{
"name": "syncfusion_flutter_charts",
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6",
"rootUri": "file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1",
"packageUri": "lib/",
"languageVersion": "3.7"
},

View File

@@ -580,8 +580,8 @@ file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/
file:///home/pierre/.pub-cache/hosted/pub.dev/string_scanner-1.4.1/lib/
syncfusion_flutter_charts
3.7
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6/lib/
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_charts-30.2.6+1/lib/
syncfusion_flutter_core
3.7
file:///home/pierre/.pub-cache/hosted/pub.dev/syncfusion_flutter_core-30.2.6/

View File

@@ -5,7 +5,7 @@
"packages": [
{
"name": "geosector_app",
"version": "3.1.4+314",
"version": "3.1.6+316",
"dependencies": [
"connectivity_plus",
"cupertino_icons",
@@ -304,7 +304,7 @@
},
{
"name": "syncfusion_flutter_charts",
"version": "30.2.6",
"version": "30.2.6+1",
"dependencies": [
"flutter",
"intl",

File diff suppressed because one or more lines are too long

View File

@@ -1325,6 +1325,126 @@ graph TD
E -->|ValueListenableBuilder| A
```
## 📱 Widgets de passages et navigation
### 🎯 PassagesListWidget
Le widget `PassagesListWidget` est le composant central pour l'affichage et la gestion des passages dans toute l'application. Il offre une expérience utilisateur cohérente avec des fonctionnalités adaptatives selon le contexte.
#### ✨ Fonctionnalités principales
- **Affichage adaptatif** : Liste complète ou tableau de bord avec fond transparent
- **Flux conditionnel de clic** : Comportement intelligent selon le type de passage
- **Bouton de création intégré** : Bouton "+" vert dans l'en-tête pour ajouter des passages
- **Filtrage avancé** : Par type, utilisateur, période, avec exclusions possibles
- **Actions contextuelles** : Modification, suppression, génération de reçus
#### 🔄 Flux conditionnel des clics sur passages
Le widget implémente un comportement intelligent lors du clic sur un passage :
```dart
// Logique de gestion des clics
void _handlePassageClick(Map<String, dynamic> passage) {
final int passageType = passage['type'] as int? ?? 1;
if (passageType == 2) {
// Type 2 (À finaliser) : Ouverture directe du formulaire d'édition
_showEditDialog(context, passageModel);
} else {
// Autres types : Affichage des détails avec option de modification
_showDetailsDialogWithEditOption(context, passage, passageModel);
}
}
```
**Comportements par type de passage :**
- **Type 1 (Réalisé)** : Affiche les détails complets avec option "Modifier"
- **Type 2 (À finaliser)** : Ouvre directement le formulaire d'édition pour finalisation rapide
- **Type 3 (Absent)** : Affiche les détails avec options limitées
- **Type 4 (Refusé)** : Affiche les détails en lecture seule
#### 🎨 Dialog de détails amélioré
La boîte de dialogue des détails a été repensée pour une meilleure lisibilité :
- **Organisation par sections** : Client, Passage, Lieu avec icônes distinctives
- **Badges colorés** : Visualisation rapide du type et statut
- **Formatage intelligent** : Dates, montants et informations structurées
- **Actions contextuelles** : Boutons adaptés selon les permissions
#### Bouton de création contextuel
Le widget intègre un bouton "+" vert flottant dans l'en-tête pour créer de nouveaux passages :
```dart
// Paramètres pour activer le bouton de création
PassagesListWidget(
showAddButton: true, // Active le bouton "+"
onAddPassage: () async {
// Logique de création avec PassageFormDialog
await _showPassageFormDialog(context);
},
)
```
**Pages avec bouton de création activé :**
- `user_field_mode_page.dart` : Mode terrain pour création rapide
- `user_history_page.dart` : Historique avec ajout possible
- `admin_history_page.dart` : Gestion administrative complète
### 🎯 DashboardAppBar - Évolution de l'interface
#### ❌ Suppression du bouton "Nouveau passage"
Le bouton global "Nouveau passage" a été **définitivement retiré** de la barre d'application (`DashboardAppBar`) pour privilégier une approche contextuelle :
**Avant :**
- Bouton toujours visible dans l'AppBar
- Création de passage possible depuis n'importe quelle page
- Confusion possible sur le contexte de création
**Après :**
- Boutons "+" contextuels dans les pages appropriées
- Création limitée aux contextes pertinents
- Interface épurée et plus intuitive
#### 🎨 Architecture simplifiée
La suppression du bouton global a permis de :
- Nettoyer les dépendances (`passage_form_dialog.dart`, `app_keys.dart`)
- Simplifier les paramètres de `DashboardLayout`
- Réduire la complexité de navigation
- Améliorer la cohérence UX
### 🎯 Mode tableau de bord
Pour les pages de tableau de bord, le `PassagesListWidget` s'adapte automatiquement :
#### 🏠 Page d'accueil utilisateur
Dans `user_dashboard_home_page.dart`, l'affichage est optimisé :
```dart
// Configuration pour le tableau de bord
SizedBox(
height: 450, // Hauteur fixe pour éviter l'overflow
child: PassagesListWidget(
passages: recentPassages,
showFilters: false, // Pas de filtres sur le dashboard
showSearch: false, // Pas de recherche
maxPassages: 20, // Limite aux 20 plus récents
transparentBackground: true, // Fond transparent pour intégration
),
)
```
**Améliorations du dashboard :**
- Suppression de la Card wrapper pour un design épuré
- Fond transparent pour intégration harmonieuse
- En-tête coloré maintenu pour la lisibilité
- Limite augmentée à 20 passages récents (au lieu de 10)
### ✨ Composants de l'architecture
#### **1. Page Parente (ex: AdminOperationsPage)**

View File

@@ -79,6 +79,9 @@ class AmicaleModel extends HiveObject {
@HiveField(24)
final String? logoBase64; // Logo en base64 (data:image/png;base64,...)
@HiveField(25)
final bool chkUserDeletePass;
AmicaleModel({
required this.id,
required this.name,
@@ -105,6 +108,7 @@ class AmicaleModel extends HiveObject {
this.chkMdpManuel = false,
this.chkUsernameManuel = false,
this.logoBase64,
this.chkUserDeletePass = false,
});
// Factory pour convertir depuis JSON (API)
@@ -139,6 +143,8 @@ class AmicaleModel extends HiveObject {
json['chk_mdp_manuel'] == 1 || json['chk_mdp_manuel'] == true;
final bool chkUsernameManuel =
json['chk_username_manuel'] == 1 || json['chk_username_manuel'] == true;
final bool chkUserDeletePass =
json['chk_user_delete_pass'] == 1 || json['chk_user_delete_pass'] == true;
// Traiter le logo si présent
String? logoBase64;
@@ -192,6 +198,7 @@ class AmicaleModel extends HiveObject {
chkMdpManuel: chkMdpManuel,
chkUsernameManuel: chkUsernameManuel,
logoBase64: logoBase64,
chkUserDeletePass: chkUserDeletePass,
);
}
@@ -222,6 +229,7 @@ class AmicaleModel extends HiveObject {
'updated_at': updatedAt?.toIso8601String(),
'chk_mdp_manuel': chkMdpManuel ? 1 : 0,
'chk_username_manuel': chkUsernameManuel ? 1 : 0,
'chk_user_delete_pass': chkUserDeletePass ? 1 : 0,
// Note: logoBase64 n'est pas envoyé via toJson (lecture seule depuis l'API)
};
}
@@ -252,6 +260,7 @@ class AmicaleModel extends HiveObject {
bool? chkMdpManuel,
bool? chkUsernameManuel,
String? logoBase64,
bool? chkUserDeletePass,
}) {
return AmicaleModel(
id: id,
@@ -279,6 +288,7 @@ class AmicaleModel extends HiveObject {
chkMdpManuel: chkMdpManuel ?? this.chkMdpManuel,
chkUsernameManuel: chkUsernameManuel ?? this.chkUsernameManuel,
logoBase64: logoBase64 ?? this.logoBase64,
chkUserDeletePass: chkUserDeletePass ?? this.chkUserDeletePass,
);
}
}

View File

@@ -42,13 +42,14 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
chkMdpManuel: fields[22] as bool,
chkUsernameManuel: fields[23] as bool,
logoBase64: fields[24] as String?,
chkUserDeletePass: fields[25] as bool,
);
}
@override
void write(BinaryWriter writer, AmicaleModel obj) {
writer
..writeByte(25)
..writeByte(26)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -98,7 +99,9 @@ class AmicaleModelAdapter extends TypeAdapter<AmicaleModel> {
..writeByte(23)
..write(obj.chkUsernameManuel)
..writeByte(24)
..write(obj.logoBase64);
..write(obj.logoBase64)
..writeByte(25)
..write(obj.chkUserDeletePass);
}
@override

View File

@@ -76,6 +76,9 @@ class ClientModel extends HiveObject {
@HiveField(23)
final bool? chkUsernameManuel;
@HiveField(24)
final bool? chkUserDeletePass;
ClientModel({
required this.id,
required this.name,
@@ -101,6 +104,7 @@ class ClientModel extends HiveObject {
this.updatedAt,
this.chkMdpManuel,
this.chkUsernameManuel,
this.chkUserDeletePass,
});
// Factory pour convertir depuis JSON (API)
@@ -148,6 +152,7 @@ class ClientModel extends HiveObject {
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']) : null,
chkMdpManuel: json['chk_mdp_manuel'] == 1 || json['chk_mdp_manuel'] == true,
chkUsernameManuel: json['chk_username_manuel'] == 1 || json['chk_username_manuel'] == true,
chkUserDeletePass: json['chk_user_delete_pass'] == 1 || json['chk_user_delete_pass'] == true,
);
}
@@ -178,6 +183,7 @@ class ClientModel extends HiveObject {
'updated_at': updatedAt?.toIso8601String(),
'chk_mdp_manuel': chkMdpManuel,
'chk_username_manuel': chkUsernameManuel,
'chk_user_delete_pass': chkUserDeletePass,
};
}
@@ -206,6 +212,7 @@ class ClientModel extends HiveObject {
DateTime? updatedAt,
bool? chkMdpManuel,
bool? chkUsernameManuel,
bool? chkUserDeletePass,
}) {
return ClientModel(
id: id,
@@ -232,6 +239,7 @@ class ClientModel extends HiveObject {
updatedAt: updatedAt ?? this.updatedAt,
chkMdpManuel: chkMdpManuel ?? this.chkMdpManuel,
chkUsernameManuel: chkUsernameManuel ?? this.chkUsernameManuel,
chkUserDeletePass: chkUserDeletePass ?? this.chkUserDeletePass,
);
}
}

View File

@@ -41,13 +41,14 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
updatedAt: fields[21] as DateTime?,
chkMdpManuel: fields[22] as bool?,
chkUsernameManuel: fields[23] as bool?,
chkUserDeletePass: fields[24] as bool?,
);
}
@override
void write(BinaryWriter writer, ClientModel obj) {
writer
..writeByte(24)
..writeByte(25)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -95,7 +96,9 @@ class ClientModelAdapter extends TypeAdapter<ClientModel> {
..writeByte(22)
..write(obj.chkMdpManuel)
..writeByte(23)
..write(obj.chkUsernameManuel);
..write(obj.chkUsernameManuel)
..writeByte(24)
..write(obj.chkUserDeletePass);
}
@override

View File

@@ -130,6 +130,9 @@ class AmicaleRepository extends ChangeNotifier {
chkAcceptSms: amicale.chkAcceptSms,
chkActive: amicale.chkActive,
chkStripe: amicale.chkStripe,
chkMdpManuel: amicale.chkMdpManuel,
chkUsernameManuel: amicale.chkUsernameManuel,
chkUserDeletePass: amicale.chkUserDeletePass,
createdAt: amicale.createdAt ?? DateTime.now(),
updatedAt: DateTime.now(),
);

View File

@@ -382,7 +382,6 @@ class _AdminDashboardPageState extends State<AdminDashboardPage> with WidgetsBin
});
},
destinations: destinations,
showNewPassageButton: false,
isAdmin: true,
body: pages[_selectedIndex],
),

View File

@@ -1,6 +1,7 @@
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
import 'package:geosector_app/core/data/models/sector_model.dart';
@@ -36,6 +37,14 @@ class DotsPainter extends CustomPainter {
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// Enum pour gérer les types de tri
enum PassageSortType {
dateDesc, // Plus récent en premier (défaut)
dateAsc, // Plus ancien en premier
addressAsc, // Adresse A-Z
addressDesc, // Adresse Z-A
}
class AdminHistoryPage extends StatefulWidget {
const AdminHistoryPage({super.key});
@@ -52,6 +61,9 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
String selectedPaymentMethod = 'Tous';
String selectedPeriod = 'Tous'; // Période par défaut
DateTimeRange? selectedDateRange;
// État du tri actuel
PassageSortType _currentSort = PassageSortType.dateDesc;
// Contrôleur pour la recherche
final TextEditingController _searchController = TextEditingController();
@@ -215,10 +227,10 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
super.dispose();
}
// Méthode pour appliquer tous les filtres
List<Map<String, dynamic>> _getFilteredPassages() {
// Nouvelle méthode pour filtrer une liste de passages déjà formatés
List<Map<String, dynamic>> _getFilteredPassagesFromList(List<Map<String, dynamic>> passages) {
try {
var filtered = _formattedPassages.where((passage) {
var filtered = passages.where((passage) {
try {
// Ne plus exclure automatiquement les passages de type 2
// car on propose maintenant un filtre par type dans les "Filtres avancés"
@@ -315,26 +327,107 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
}
}).toList();
// Trier par date décroissante (plus récent en premier)
filtered.sort((a, b) {
try {
final DateTime dateA = a['date'] as DateTime;
final DateTime dateB = b['date'] as DateTime;
return dateB.compareTo(dateA);
} catch (e) {
return 0;
}
});
// Appliquer le tri sélectionné
filtered = _sortPassages(filtered);
debugPrint(
'Passages filtrés: ${filtered.length}/${_formattedPassages.length}');
'Passages filtrés: ${filtered.length}/${passages.length}');
return filtered;
} catch (e) {
debugPrint('Erreur globale lors du filtrage: $e');
return _formattedPassages;
return passages;
}
}
// Méthode pour trier les passages selon le type de tri sélectionné
List<Map<String, dynamic>> _sortPassages(List<Map<String, dynamic>> passages) {
final sortedPassages = List<Map<String, dynamic>>.from(passages);
switch (_currentSort) {
case PassageSortType.dateDesc:
sortedPassages.sort((a, b) {
try {
return (b['date'] as DateTime).compareTo(a['date'] as DateTime);
} catch (e) {
return 0;
}
});
break;
case PassageSortType.dateAsc:
sortedPassages.sort((a, b) {
try {
return (a['date'] as DateTime).compareTo(b['date'] as DateTime);
} catch (e) {
return 0;
}
});
break;
case PassageSortType.addressAsc:
sortedPassages.sort((a, b) {
try {
// Tri intelligent par rue, numéro (numérique), rueBis
final String rueA = a['rue'] ?? '';
final String rueB = b['rue'] ?? '';
final String numeroA = a['numero'] ?? '';
final String numeroB = b['numero'] ?? '';
final String rueBisA = a['rueBis'] ?? '';
final String rueBisB = b['rueBis'] ?? '';
// D'abord comparer les rues
int rueCompare = rueA.toLowerCase().compareTo(rueB.toLowerCase());
if (rueCompare != 0) return rueCompare;
// Si les rues sont identiques, comparer les numéros (numériquement)
int numA = int.tryParse(numeroA) ?? 0;
int numB = int.tryParse(numeroB) ?? 0;
int numCompare = numA.compareTo(numB);
if (numCompare != 0) return numCompare;
// Si les numéros sont identiques, comparer les rueBis
return rueBisA.toLowerCase().compareTo(rueBisB.toLowerCase());
} catch (e) {
return 0;
}
});
break;
case PassageSortType.addressDesc:
sortedPassages.sort((a, b) {
try {
// Tri intelligent inversé par rue, numéro (numérique), rueBis
final String rueA = a['rue'] ?? '';
final String rueB = b['rue'] ?? '';
final String numeroA = a['numero'] ?? '';
final String numeroB = b['numero'] ?? '';
final String rueBisA = a['rueBis'] ?? '';
final String rueBisB = b['rueBis'] ?? '';
// D'abord comparer les rues (inversé)
int rueCompare = rueB.toLowerCase().compareTo(rueA.toLowerCase());
if (rueCompare != 0) return rueCompare;
// Si les rues sont identiques, comparer les numéros (inversé numériquement)
int numA = int.tryParse(numeroA) ?? 0;
int numB = int.tryParse(numeroB) ?? 0;
int numCompare = numB.compareTo(numA);
if (numCompare != 0) return numCompare;
// Si les numéros sont identiques, comparer les rueBis (inversé)
return rueBisB.toLowerCase().compareTo(rueBisA.toLowerCase());
} catch (e) {
return 0;
}
});
break;
}
return sortedPassages;
}
// Méthode pour appliquer tous les filtres (utilisée dans le build)
List<Map<String, dynamic>> _getFilteredPassages() {
return _getFilteredPassagesFromList(_formattedPassages);
}
// Mettre à jour le filtre par secteur
void _updateSectorFilter(String sectorName, int? sectorId) {
setState(() {
@@ -465,29 +558,135 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
const SizedBox(height: 16),
// Widget de liste des passages avec hauteur fixe
// Widget de liste des passages avec hauteur fixe et ValueListenableBuilder
SizedBox(
height: constraints.maxHeight * 0.7, // 70% de la hauteur disponible
child: PassagesListWidget(
passages: passages,
showFilters:
false, // Désactivé car les filtres sont maintenant dans la card "Filtres avancés"
showSearch:
false, // Désactivé car la recherche est maintenant dans la card "Filtres avancés"
showActions: true,
// Ne plus passer les filtres individuels car ils sont maintenant appliqués dans _getFilteredPassages()
onPassageSelected: (passage) {
_openPassageEditDialog(context, passage);
},
onReceiptView: (passage) {
_showReceiptDialog(context, passage);
},
onDetailsView: (passage) {
_showDetailsDialog(context, passage);
},
onPassageEdit: (passage) {
// Action pour modifier le passage
// Cette fonctionnalité pourrait être implémentée ultérieurement
child: ValueListenableBuilder(
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
builder: (context, Box<PassageModel> passagesBox, child) {
// Reconvertir les passages à chaque changement
final List<PassageModel> allPassages = passagesBox.values.toList();
// Convertir et formater les passages
final formattedPassages = _formatPassagesForWidget(
allPassages,
_sectorRepository,
_membreRepository
);
// Appliquer les filtres
final filteredPassages = _getFilteredPassagesFromList(formattedPassages);
return PassagesListWidget(
showAddButton: true, // Activer le bouton de création
onAddPassage: () async {
// Ouvrir le dialogue de création de passage
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return PassageFormDialog(
title: 'Nouveau passage',
passageRepository: _passageRepository,
userRepository: _userRepository,
operationRepository: operationRepository,
onSuccess: () {
// Le widget se rafraîchira automatiquement via ValueListenableBuilder
},
);
},
);
},
sortingButtons: Row(
children: [
// Bouton tri par date avec icône calendrier
IconButton(
icon: Icon(
Icons.calendar_today,
size: 20,
color: _currentSort == PassageSortType.dateDesc ||
_currentSort == PassageSortType.dateAsc
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
tooltip: _currentSort == PassageSortType.dateAsc
? 'Tri par date (ancien en premier)'
: 'Tri par date (récent en premier)',
onPressed: () {
setState(() {
if (_currentSort == PassageSortType.dateDesc) {
_currentSort = PassageSortType.dateAsc;
} else {
_currentSort = PassageSortType.dateDesc;
}
});
},
),
// Indicateur de direction pour la date
if (_currentSort == PassageSortType.dateDesc ||
_currentSort == PassageSortType.dateAsc)
Icon(
_currentSort == PassageSortType.dateAsc
? Icons.arrow_upward
: Icons.arrow_downward,
size: 14,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 4),
// Bouton tri par adresse avec icône maison
IconButton(
icon: Icon(
Icons.home,
size: 20,
color: _currentSort == PassageSortType.addressDesc ||
_currentSort == PassageSortType.addressAsc
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
tooltip: _currentSort == PassageSortType.addressAsc
? 'Tri par adresse (A-Z)'
: 'Tri par adresse (Z-A)',
onPressed: () {
setState(() {
if (_currentSort == PassageSortType.addressAsc) {
_currentSort = PassageSortType.addressDesc;
} else {
_currentSort = PassageSortType.addressAsc;
}
});
},
),
// Indicateur de direction pour l'adresse
if (_currentSort == PassageSortType.addressDesc ||
_currentSort == PassageSortType.addressAsc)
Icon(
_currentSort == PassageSortType.addressAsc
? Icons.arrow_upward
: Icons.arrow_downward,
size: 14,
color: Theme.of(context).colorScheme.primary,
),
],
),
passages: filteredPassages,
showFilters: false,
showSearch: false,
showActions: true,
// Le widget gère maintenant le flux conditionnel par défaut
onPassageSelected: null,
onReceiptView: (passage) {
_showReceiptDialog(context, passage);
},
onDetailsView: (passage) {
_showDetailsDialog(context, passage);
},
onPassageEdit: (passage) {
// Action pour modifier le passage
},
onPassageDelete: (passage) {
_showDeleteConfirmationDialog(passage);
},
);
},
),
),
@@ -584,6 +783,9 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
// Déterminer si le passage a une erreur d'envoi de reçu
final bool hasError = passage.emailErreur.isNotEmpty;
// Récupérer l'ID de l'utilisateur courant pour déterminer la propriété
final currentUserId = _userRepository.getCurrentUser()?.id;
return {
'id': passage.id,
if (passage.passedAt != null) 'date': passage.passedAt!,
@@ -618,6 +820,7 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
'lastSyncedAt': passage.lastSyncedAt,
'isActive': passage.isActive,
'isSynced': passage.isSynced,
'isOwnedByCurrentUser': passage.fkUser == currentUserId, // Ajout du champ pour le widget
};
}).toList();
}
@@ -653,6 +856,116 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
);
}
// Afficher les détails avec option de modification
void _showDetailsDialogWithEditOption(BuildContext context, Map<String, dynamic> passage, PassageModel passageModel) {
final int passageId = passage['id'] as int;
final DateTime date = passage['date'] as DateTime;
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: Row(
children: [
const Icon(Icons.info_outline, color: Colors.blue),
const SizedBox(width: 8),
Text('Détails du passage #$passageId'),
],
),
content: SizedBox(
width: 500,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildDetailRow('Date',
'${date.day}/${date.month}/${date.year} à ${date.hour}h${date.minute.toString().padLeft(2, '0')}'),
_buildDetailRow('Adresse', passage['address'] as String),
_buildDetailRow('Secteur', passage['sector'] as String),
_buildDetailRow('Collecteur', passage['user'] as String),
_buildDetailRow(
'Type',
AppKeys.typesPassages[passage['type']]?['titre'] ??
'Inconnu'),
_buildDetailRow('Montant', '${passage['amount']}'),
_buildDetailRow(
'Mode de paiement',
AppKeys.typesReglements[passage['payment']]?['titre'] ??
'Inconnu'),
_buildDetailRow('Email', passage['email'] as String),
_buildDetailRow(
'Reçu envoyé', passage['hasReceipt'] ? 'Oui' : 'Non'),
_buildDetailRow(
'Erreur d\'envoi', passage['hasError'] ? 'Oui' : 'Non'),
_buildDetailRow(
'Notes',
(passage['notes'] as String).isEmpty
? '-'
: passage['notes'] as String),
const SizedBox(height: 16),
const Text(
'Historique des actions',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHistoryItem(
date,
passage['user'] as String,
'Création du passage',
),
if (passage['hasReceipt'])
_buildHistoryItem(
date.add(const Duration(minutes: 5)),
'Système',
'Envoi du reçu par email',
),
if (passage['hasError'])
_buildHistoryItem(
date.add(const Duration(minutes: 6)),
'Système',
'Erreur lors de l\'envoi du reçu',
),
],
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('Fermer'),
),
ElevatedButton.icon(
icon: const Icon(Icons.edit),
onPressed: () {
// Fermer le dialog de détails
Navigator.pop(dialogContext);
// Ouvrir le formulaire de modification
_showEditDialog(context, passageModel);
},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
),
label: const Text('Modifier'),
),
],
),
);
}
// Méthode pour conserver l'ancienne _showDetailsDialog pour les autres usages
void _showDetailsDialog(BuildContext context, Map<String, dynamic> passage) {
final int passageId = passage['id'] as int;
final DateTime date = passage['date'] as DateTime;
@@ -736,13 +1049,6 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
onPressed: () => Navigator.pop(context),
child: const Text('Fermer'),
),
ElevatedButton(
onPressed: () {
// Action pour modifier le passage
Navigator.pop(context);
},
child: const Text('Modifier'),
),
],
),
);
@@ -753,9 +1059,10 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
try {
debugPrint('=== DEBUT _openPassageEditDialog ===');
// Récupérer l'ID du passage
// Récupérer l'ID et le type du passage
final int passageId = passage['id'] as int;
debugPrint('Recherche du passage ID: $passageId');
final int passageType = passage['type'] as int? ?? 1;
debugPrint('Passage ID: $passageId, Type: $passageType');
// Trouver le PassageModel original dans la liste
final PassageModel? passageModel =
@@ -771,23 +1078,17 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
return;
}
debugPrint('Ouverture du dialog...');
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => PassageFormDialog(
passage: passageModel,
title: 'Modifier le passage',
passageRepository: _passageRepository,
userRepository: _userRepository,
operationRepository: operationRepository,
onSuccess: () {
debugPrint('Dialog fermé avec succès');
// Recharger les données après modification
_loadPassages();
},
),
);
// Flux conditionnel selon le type de passage
if (passageType == 2) {
// Type 2 ("À finaliser") : Ouvrir directement le formulaire de modification
debugPrint('Passage type 2 - Ouverture directe du formulaire');
_showEditDialog(context, passageModel);
} else {
// Autres types : Afficher d'abord les détails avec option de modification
debugPrint('Passage type $passageType - Affichage des détails d\'abord');
_showDetailsDialogWithEditOption(context, passage, passageModel);
}
debugPrint('=== FIN _openPassageEditDialog ===');
} catch (e, stackTrace) {
debugPrint('=== ERREUR _openPassageEditDialog ===');
@@ -797,13 +1098,35 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de l\'ouverture du formulaire: $e'),
content: Text('Erreur lors de l\'ouverture: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
// Méthode extraite pour ouvrir le dialog de modification
void _showEditDialog(BuildContext context, PassageModel passageModel) {
debugPrint('Ouverture du formulaire de modification pour le passage ${passageModel.id}');
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => PassageFormDialog(
passage: passageModel,
title: 'Modifier le passage',
passageRepository: _passageRepository,
userRepository: _userRepository,
operationRepository: operationRepository,
onSuccess: () {
debugPrint('Dialog fermé avec succès');
// Recharger les données après modification
_loadPassages();
},
),
);
}
Widget _buildDetailRow(String label, String value) {
return Padding(
@@ -1334,6 +1657,209 @@ class _AdminHistoryPageState extends State<AdminHistoryPage> {
);
}
// Afficher le dialog de confirmation de suppression
void _showDeleteConfirmationDialog(Map<String, dynamic> passage) {
final TextEditingController confirmController = TextEditingController();
// Récupérer l'ID du passage et trouver le PassageModel original
final int passageId = passage['id'] as int;
final PassageModel? passageModel =
_originalPassages.where((p) => p.id == passageId).firstOrNull;
if (passageModel == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Impossible de trouver le passage'),
backgroundColor: Colors.red,
),
);
return;
}
final String streetNumber = passageModel.numero ?? '';
final String fullAddress = '${passageModel.numero ?? ''} ${passageModel.rueBis ?? ''} ${passageModel.rue ?? ''}'.trim();
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: const Row(
children: [
Icon(Icons.warning, color: Colors.red, size: 28),
SizedBox(width: 8),
Text('Confirmation de suppression'),
],
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'ATTENTION : Cette action est irréversible !',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
fontSize: 16,
),
),
const SizedBox(height: 16),
Text(
'Vous êtes sur le point de supprimer définitivement le passage :',
style: TextStyle(color: Colors.grey[800]),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fullAddress.isEmpty ? 'Adresse inconnue' : fullAddress,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
const SizedBox(height: 4),
if (passage['user'] != null)
Text(
'Collecteur: ${passage['user']}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
if (passage['date'] != null)
Text(
'Date: ${_formatDate(passage['date'] as DateTime)}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
const SizedBox(height: 20),
const Text(
'Pour confirmer la suppression, veuillez saisir le numéro de rue de ce passage :',
style: TextStyle(fontWeight: FontWeight.w500),
),
const SizedBox(height: 12),
TextField(
controller: confirmController,
decoration: InputDecoration(
labelText: 'Numéro de rue',
hintText: streetNumber.isNotEmpty ? 'Ex: $streetNumber' : 'Saisir le numéro',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.home),
),
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.characters,
),
],
),
),
actions: [
TextButton(
onPressed: () {
confirmController.dispose();
Navigator.of(dialogContext).pop();
},
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () async {
// Vérifier que le numéro saisi correspond
final enteredNumber = confirmController.text.trim();
if (enteredNumber.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Veuillez saisir le numéro de rue'),
backgroundColor: Colors.orange,
),
);
return;
}
if (streetNumber.isNotEmpty && enteredNumber.toUpperCase() != streetNumber.toUpperCase()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Le numéro de rue ne correspond pas'),
backgroundColor: Colors.red,
),
);
return;
}
// Fermer le dialog
confirmController.dispose();
Navigator.of(dialogContext).pop();
// Effectuer la suppression
await _deletePassage(passageModel);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Supprimer définitivement'),
),
],
);
},
);
}
// Supprimer un passage
Future<void> _deletePassage(PassageModel passage) async {
try {
// Appeler le repository pour supprimer via l'API
final success = await _passageRepository.deletePassageViaApi(passage.id);
if (success && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Passage supprimé avec succès'),
backgroundColor: Colors.green,
),
);
// Pas besoin de recharger, le ValueListenableBuilder
// se rafraîchira automatiquement après la suppression dans Hive
} else if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Erreur lors de la suppression du passage'),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
debugPrint('Erreur suppression passage: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
// Formater une date
String _formatDate(DateTime date) {
return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}';
}
// Construction du filtre par mode de règlement
Widget _buildPaymentFilter(ThemeData theme) {
return Column(

View File

@@ -20,6 +20,7 @@ import 'package:geosector_app/core/services/api_service.dart';
import 'package:geosector_app/core/services/current_user_service.dart';
import 'package:geosector_app/core/repositories/operation_repository.dart';
import 'package:geosector_app/core/services/data_loading_service.dart';
import 'package:geosector_app/presentation/widgets/passage_map_dialog.dart';
class AdminMapPage extends StatefulWidget {
const AdminMapPage({super.key});
@@ -991,148 +992,16 @@ class _AdminMapPageState extends State<AdminMapPage> {
// Afficher les informations d'un passage lorsqu'on clique dessus
void _showPassageInfo(Map<String, dynamic> passage) {
final PassageModel passageModel = passage['model'] as PassageModel;
final int type = passageModel.fkType;
// Construire l'adresse complète
final String adresse = '${passageModel.numero}, ${passageModel.rueBis} ${passageModel.rue}';
// Informations sur l'étage, l'appartement et la résidence (si habitat = 2)
String? etageInfo;
String? apptInfo;
String? residenceInfo;
if (passageModel.fkHabitat == 2) {
if (passageModel.niveau.isNotEmpty) {
etageInfo = 'Etage ${passageModel.niveau}';
}
if (passageModel.appt.isNotEmpty) {
apptInfo = 'appt. ${passageModel.appt}';
}
if (passageModel.residence.isNotEmpty) {
residenceInfo = passageModel.residence;
}
}
// Formater la date (uniquement si le type n'est pas 2 et si la date existe)
String dateInfo = '';
if (type != 2 && passageModel.passedAt != null) {
dateInfo = 'Date: ${_formatDate(passageModel.passedAt!)}';
}
// Récupérer le nom du passage (si le type n'est pas 6 - Maison vide)
String? nomInfo;
if (type != 6 && passageModel.name.isNotEmpty) {
nomInfo = passageModel.name;
}
// Récupérer les informations de règlement si le type est 1 (Effectué) ou 5 (Lot)
Widget? reglementInfo;
if (type == 1 || type == 5) {
final int typeReglementId = passageModel.fkTypeReglement;
final String montant = passageModel.montant;
// Récupérer les informations du type de règlement
if (AppKeys.typesReglements.containsKey(typeReglementId)) {
final Map<String, dynamic> typeReglement = AppKeys.typesReglements[typeReglementId]!;
final String titre = typeReglement['titre'] as String;
final Color couleur = Color(typeReglement['couleur'] as int);
final IconData iconData = typeReglement['icon_data'] as IconData;
reglementInfo = Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
children: [
Icon(iconData, color: couleur, size: 20),
const SizedBox(width: 8),
Text('$titre: $montant', style: TextStyle(color: couleur, fontWeight: FontWeight.bold)),
],
),
);
}
}
// Afficher une bulle d'information
showDialog(
context: context,
builder: (context) => AlertDialog(
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 0),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Afficher en premier si le passage n'est pas affecté à un secteur
if (passageModel.fkSector == null) ...[
Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
border: Border.all(color: Colors.red, width: 1),
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
const Icon(Icons.warning, color: Colors.red, size: 20),
const SizedBox(width: 8),
const Expanded(
child: Text(
'Ce passage n\'est plus affecté à un secteur',
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
),
),
],
),
),
],
Text('Adresse: $adresse'),
if (residenceInfo != null) ...[const SizedBox(height: 4), Text(residenceInfo)],
if (etageInfo != null) ...[const SizedBox(height: 4), Text(etageInfo)],
if (apptInfo != null) ...[const SizedBox(height: 4), Text(apptInfo)],
if (dateInfo.isNotEmpty) ...[const SizedBox(height: 8), Text(dateInfo)],
if (nomInfo != null) ...[const SizedBox(height: 8), Text('Nom: $nomInfo')],
if (reglementInfo != null) reglementInfo,
],
),
actionsPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// Bouton d'édition
IconButton(
onPressed: () {
Navigator.of(context).pop();
// Logique pour éditer le passage
debugPrint('Éditer le passage ${passageModel.id}');
},
icon: const Icon(Icons.edit),
color: Colors.blue,
tooltip: 'Modifier',
),
// Bouton de suppression
IconButton(
onPressed: () {
Navigator.of(context).pop();
// Logique pour supprimer le passage
debugPrint('Supprimer le passage ${passageModel.id}');
},
icon: const Icon(Icons.delete),
color: Colors.red,
tooltip: 'Supprimer',
),
],
),
// Bouton de fermeture
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Fermer'),
),
],
),
],
builder: (context) => PassageMapDialog(
passage: passageModel,
isAdmin: true,
onDeleted: () {
// Recharger les passages après suppression
_loadPassages();
},
),
);
}

View File

@@ -176,76 +176,92 @@ Widget _buildCombinedPaymentsCard(bool isDesktop) {
// Construction de la liste des derniers passages
Widget _buildRecentPassages(BuildContext context, ThemeData theme) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 0.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Derniers passages',
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
// Utilisation directe du widget PassagesListWidget sans Card wrapper
return ValueListenableBuilder(
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
builder: (context, Box<PassageModel> passagesBox, child) {
final recentPassages = _getRecentPassages(passagesBox);
// Debug : afficher le nombre de passages récupérés
debugPrint('UserDashboardHomePage: ${recentPassages.length} passages récents récupérés');
if (recentPassages.isEmpty) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: const Padding(
padding: EdgeInsets.all(32.0),
child: Center(
child: Text(
'Aucun passage récent',
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
TextButton(
onPressed: () {
// Naviguer vers la page d'historique
},
child: const Text('Voir tout'),
),
],
),
),
),
// Utilisation du widget commun PassagesListWidget avec ValueListenableBuilder
ValueListenableBuilder(
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
builder: (context, Box<PassageModel> passagesBox, child) {
final recentPassages = _getRecentPassages(passagesBox);
);
}
return PassagesListWidget(
passages: recentPassages,
showFilters: false,
showSearch: false,
showActions: true,
maxPassages: 10,
excludePassageTypes: const [2],
filterByUserId: userRepository.getCurrentUser()?.id,
periodFilter: 'last15',
onPassageSelected: (passage) {
debugPrint('Passage sélectionné: ${passage['id']}');
},
onDetailsView: (passage) {
debugPrint('Affichage des détails: ${passage['id']}');
},
onPassageEdit: (passage) {
debugPrint('Modification du passage: ${passage['id']}');
},
onReceiptView: (passage) {
debugPrint('Affichage du reçu pour le passage: ${passage['id']}');
},
);
// Utiliser une hauteur fixe pour le widget dans le dashboard
return SizedBox(
height: 450, // Hauteur légèrement augmentée pour compenser l'absence de Card
child: PassagesListWidget(
passages: recentPassages,
showFilters: false,
showSearch: false,
showActions: true,
maxPassages: 20,
// Ne pas appliquer de filtres supplémentaires car les passages
// sont déjà filtrés dans _getRecentPassages
excludePassageTypes: null, // Pas de filtre, déjà géré dans _getRecentPassages
filterByUserId: null, // Pas de filtre, déjà géré dans _getRecentPassages
periodFilter: null, // Pas de filtre de période
// Le widget gère maintenant le flux conditionnel par défaut
onPassageSelected: null,
onDetailsView: (passage) {
debugPrint('Affichage des détails: ${passage['id']}');
},
onPassageEdit: (passage) {
debugPrint('Modification du passage: ${passage['id']}');
},
onReceiptView: (passage) {
debugPrint('Affichage du reçu pour le passage: ${passage['id']}');
},
onPassageDelete: (passage) {
// Pas besoin de faire quoi que ce soit ici
// Le ValueListenableBuilder se rafraîchira automatiquement
// après la suppression dans Hive via le repository
},
),
],
),
);
},
);
}
/// Récupère les passages récents pour la liste
List<Map<String, dynamic>> _getRecentPassages(Box<PassageModel> passagesBox) {
final allPassages = passagesBox.values.where((p) => p.passedAt != null).toList();
final currentUserId = userRepository.getCurrentUser()?.id;
// Filtrer les passages :
// - Avoir une date passedAt
// - Exclure le type 2 ("À finaliser")
// - Appartenir à l'utilisateur courant
final allPassages = passagesBox.values.where((p) {
if (p.passedAt == null) return false;
if (p.fkType == 2) return false; // Exclure les passages "À finaliser"
if (currentUserId != null && p.fkUser != currentUserId) return false; // Filtrer par utilisateur
return true;
}).toList();
// Trier par date décroissante
allPassages.sort((a, b) => b.passedAt!.compareTo(a.passedAt!));
// Limiter aux 10 passages les plus récents
final recentPassagesModels = allPassages.take(10).toList();
// Limiter aux 20 passages les plus récents
final recentPassagesModels = allPassages.take(20).toList();
// Convertir les modèles de passage au format attendu par le widget PassagesListWidget
return recentPassagesModels.map((passage) {
@@ -278,6 +294,7 @@ Widget _buildCombinedPaymentsCard(bool isDesktop) {
'hasReceipt': passage.nomRecu.isNotEmpty,
'hasError': passage.emailErreur.isNotEmpty,
'fkUser': passage.fkUser,
'isOwnedByCurrentUser': passage.fkUser == userRepository.getCurrentUser()?.id, // Ajout du champ pour le widget
};
}).toList();
}

View File

@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/presentation/widgets/dashboard_layout.dart';
import 'package:geosector_app/presentation/widgets/passages/passage_form.dart';
import 'package:geosector_app/presentation/widgets/badged_navigation_destination.dart';
// Import des pages utilisateur
@@ -109,7 +108,6 @@ class _UserDashboardPageState extends State<UserDashboardPage> {
label: 'Accès restreint',
),
],
showNewPassageButton: false,
body: _buildNoOperationMessage(context),
);
}
@@ -128,7 +126,6 @@ class _UserDashboardPageState extends State<UserDashboardPage> {
label: 'Accès restreint',
),
],
showNewPassageButton: false,
body: _buildNoSectorMessage(context),
);
}
@@ -176,7 +173,6 @@ class _UserDashboardPageState extends State<UserDashboardPage> {
label: 'Terrain',
),
],
onNewPassagePressed: () => _showPassageForm(context),
body: _pages[_selectedIndex],
);
}
@@ -282,95 +278,4 @@ class _UserDashboardPageState extends State<UserDashboardPage> {
}
// Affiche le formulaire de passage
void _showPassageForm(BuildContext context) {
final theme = Theme.of(context);
showDialog(
context: context,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Container(
constraints: const BoxConstraints(
maxWidth: 600,
maxHeight: 700,
),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête de la modale
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Nouveau passage',
style: theme.textTheme.headlineSmall?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
],
),
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
// Formulaire de passage
Expanded(
child: SingleChildScrollView(
child: PassageForm(
onSubmit: (formData) {
// Traiter les données du formulaire
_handlePassageSubmission(context, formData);
},
),
),
),
],
),
),
),
);
}
// Traiter la soumission du formulaire de passage
void _handlePassageSubmission(
BuildContext context, Map<String, dynamic> formData) {
// Fermer la modale
Navigator.of(context).pop();
// Ici vous pouvez traiter les données du formulaire
// Par exemple, les envoyer au repository ou à un service
// Pour l'instant, afficher un message de succès
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('Passage enregistré avec succès pour ${formData['adresse']}'),
backgroundColor: Theme.of(context).colorScheme.primary,
behavior: SnackBarBehavior.floating,
),
);
// TODO: Intégrer avec votre logique métier
// Exemple :
// try {
// await passageRepository.createPassage(formData);
// // Rafraîchir les données si nécessaire
// } catch (e) {
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text('Erreur lors de l\'enregistrement: $e'),
// backgroundColor: Theme.of(context).colorScheme.error,
// ),
// );
// }
}
}

View File

@@ -14,8 +14,10 @@ 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/current_amicale_service.dart';
import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart';
import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart';
import 'package:geosector_app/app.dart';
import 'package:sensors_plus/sensors_plus.dart';
import 'package:geosector_app/core/utils/api_exception.dart';
class UserFieldModePage extends StatefulWidget {
const UserFieldModePage({super.key});
@@ -372,6 +374,164 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
),
);
}
// Vérifier si l'amicale autorise la suppression des passages
bool _canDeletePassages() {
try {
final amicale = CurrentAmicaleService.instance.currentAmicale;
if (amicale != null) {
return amicale.chkUserDeletePass == true;
}
} catch (e) {
debugPrint('Erreur lors de la vérification des permissions de suppression: $e');
}
return false;
}
// Afficher le dialog de confirmation de suppression
void _showDeleteConfirmationDialog(PassageModel passage) {
final TextEditingController confirmController = TextEditingController();
final String streetNumber = passage.numero ?? '';
final String fullAddress = '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim();
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: const Row(
children: [
Icon(Icons.warning, color: Colors.red, size: 28),
SizedBox(width: 8),
Text('Confirmation de suppression'),
],
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'ATTENTION : Cette action est irréversible !',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
fontSize: 16,
),
),
const SizedBox(height: 16),
Text(
'Vous êtes sur le point de supprimer définitivement le passage :',
style: TextStyle(color: Colors.grey[800]),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!),
),
child: Text(
fullAddress.isEmpty ? 'Adresse inconnue' : fullAddress,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
const SizedBox(height: 20),
const Text(
'Pour confirmer la suppression, veuillez saisir le numéro de rue de ce passage :',
style: TextStyle(fontWeight: FontWeight.w500),
),
const SizedBox(height: 12),
TextField(
controller: confirmController,
decoration: InputDecoration(
labelText: 'Numéro de rue',
hintText: streetNumber.isNotEmpty ? 'Ex: $streetNumber' : 'Saisir le numéro',
border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.home),
),
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.characters,
),
],
),
),
actions: [
TextButton(
onPressed: () {
confirmController.dispose();
Navigator.of(dialogContext).pop();
},
child: const Text('Annuler'),
),
ElevatedButton(
onPressed: () async {
// Vérifier que le numéro saisi correspond
final enteredNumber = confirmController.text.trim();
if (enteredNumber.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Veuillez saisir le numéro de rue'),
backgroundColor: Colors.orange,
),
);
return;
}
if (streetNumber.isNotEmpty && enteredNumber.toUpperCase() != streetNumber.toUpperCase()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Le numéro de rue ne correspond pas'),
backgroundColor: Colors.red,
),
);
return;
}
// Fermer le dialog
confirmController.dispose();
Navigator.of(dialogContext).pop();
// Effectuer la suppression
await _deletePassage(passage);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Supprimer définitivement'),
),
],
);
},
);
}
// Supprimer un passage
Future<void> _deletePassage(PassageModel passage) async {
try {
// Appeler le repository pour supprimer via l'API
final success = await passageRepository.deletePassageViaApi(passage.id);
if (success && mounted) {
ApiException.showSuccess(context, 'Passage supprimé avec succès');
// Rafraîchir la liste des passages
_updateNearbyPassages();
} else if (mounted) {
ApiException.showError(context, Exception('Erreur lors de la suppression'));
}
} catch (e) {
debugPrint('Erreur suppression passage: $e');
if (mounted) {
ApiException.showError(context, e);
}
}
}
@override
void dispose() {
@@ -854,149 +1014,112 @@ class _UserFieldModePageState extends State<UserFieldModePage> with TickerProvid
}).toList();
}
List<PassageModel> _getFilteredPassages() {
if (_searchQuery.isEmpty) {
return _nearbyPassages;
}
return _nearbyPassages.where((passage) {
// Construire l'adresse à partir des champs disponibles
final address = '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim().toLowerCase();
return address.contains(_searchQuery);
List<Map<String, dynamic>> _getFilteredPassages() {
// Filtrer d'abord par recherche si nécessaire
List<PassageModel> filtered = _searchQuery.isEmpty
? _nearbyPassages
: _nearbyPassages.where((passage) {
final address = '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim().toLowerCase();
return address.contains(_searchQuery);
}).toList();
// Convertir au format attendu par PassagesListWidget avec distance
return filtered.map((passage) {
// Calculer la distance
final double lat = double.tryParse(passage.gpsLat ?? '0') ?? 0;
final double lng = double.tryParse(passage.gpsLng ?? '0') ?? 0;
final distance = _currentPosition != null
? _calculateDistance(
_currentPosition!.latitude,
_currentPosition!.longitude,
lat,
lng,
)
: 0.0;
// Construire l'adresse complète
final String address = '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim();
// Convertir le montant
double amount = 0.0;
try {
if (passage.montant.isNotEmpty) {
String montantStr = passage.montant.replaceAll(',', '.');
amount = double.tryParse(montantStr) ?? 0.0;
}
} catch (e) {
// Ignorer les erreurs de conversion
}
return {
'id': passage.id,
'address': address.isEmpty ? 'Adresse inconnue' : address,
'amount': amount,
'date': passage.passedAt ?? DateTime.now(),
'type': passage.fkType,
'payment': passage.fkTypeReglement,
'name': passage.name,
'notes': passage.remarque,
'hasReceipt': passage.nomRecu.isNotEmpty,
'hasError': passage.emailErreur.isNotEmpty,
'fkUser': passage.fkUser,
'distance': distance, // Ajouter la distance pour le tri et l'affichage
'nbPassages': passage.nbPassages, // Pour la couleur de l'indicateur
'isOwnedByCurrentUser': passage.fkUser == userRepository.getCurrentUser()?.id, // Ajout du champ pour le widget
// Garder les données originales pour l'édition
'numero': passage.numero,
'rueBis': passage.rueBis,
'rue': passage.rue,
'ville': passage.ville,
};
}).toList();
}
Widget _buildPassagesList() {
final filteredPassages = _getFilteredPassages();
if (filteredPassages.isEmpty) {
return Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search_off, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
_searchQuery.isNotEmpty
? 'Aucun passage trouvé pour "$_searchQuery"'
: 'Aucun passage à proximité',
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
);
}
return Container(
color: Colors.white,
child: ListView.separated(
padding: const EdgeInsets.only(bottom: 20),
itemCount: filteredPassages.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
final passage = filteredPassages[index];
// Convertir les coordonnées GPS string en double
final double lat = double.tryParse(passage.gpsLat ?? '0') ?? 0;
final double lng = double.tryParse(passage.gpsLng ?? '0') ?? 0;
final distance = _currentPosition != null
? _calculateDistance(
_currentPosition!.latitude,
_currentPosition!.longitude,
lat,
lng,
)
: 0.0;
// Formater la distance
String distanceText;
if (distance < 1000) {
distanceText = '${distance.toStringAsFixed(0)} m';
} else {
distanceText = '${(distance / 1000).toStringAsFixed(1)} km';
}
// Couleur selon nbPassages
Color indicatorColor;
if (passage.nbPassages == 0) {
indicatorColor = Colors.grey[400]!;
} else if (passage.nbPassages == 1) {
indicatorColor = const Color(0xFFF7A278);
} else {
indicatorColor = const Color(0xFFE65100);
}
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: indicatorColor,
border: Border.all(color: const Color(0xFFF7A278), width: 2), // couleur2: Orange
),
),
title: Text(
'${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim().isEmpty
? 'Adresse inconnue'
: '${passage.numero ?? ''} ${passage.rueBis ?? ''} ${passage.rue ?? ''}'.trim(),
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Row(
children: [
Icon(Icons.navigation, size: 14, color: Colors.green[600]),
const SizedBox(width: 4),
Text(
distanceText,
style: TextStyle(
color: Colors.green[700],
fontWeight: FontWeight.w500,
fontSize: 13,
),
),
if (passage.name != null && passage.name!.isNotEmpty) ...[
const SizedBox(width: 12),
Expanded(
child: Text(
passage.name!,
style: TextStyle(
color: Colors.grey[600],
fontSize: 13,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
],
),
trailing: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green[50],
shape: BoxShape.circle,
),
child: Icon(
Icons.arrow_forward_ios,
size: 16,
color: Colors.green[700],
),
),
onTap: () {
HapticFeedback.lightImpact();
_openPassageForm(passage);
child: PassagesListWidget(
passages: filteredPassages,
showFilters: false, // Pas de filtres, juste la liste
showSearch: false, // La recherche est déjà dans l'interface
showActions: true,
sortBy: 'distance', // Tri par distance pour le mode terrain
excludePassageTypes: const [], // Afficher tous les types (notamment le type 2)
showAddButton: true, // Activer le bouton de création
// Le widget gère maintenant le flux conditionnel par défaut
onPassageSelected: null,
onAddPassage: () async {
// Ouvrir le dialogue de création de passage
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return PassageFormDialog(
title: 'Nouveau passage',
passageRepository: passageRepository,
userRepository: userRepository,
operationRepository: operationRepository,
onSuccess: () {
// Le widget se rafraîchira automatiquement via ValueListenableBuilder
},
);
},
);
},
onPassageDelete: _canDeletePassages()
? (passage) {
// Retrouver le PassageModel original pour la suppression
final passageId = passage['id'] as int;
final originalPassage = _nearbyPassages.firstWhere(
(p) => p.id == passageId,
orElse: () => _nearbyPassages.first,
);
_showDeleteConfirmationDialog(originalPassage);
}
: null,
),
);
}

View File

@@ -1,7 +1,9 @@
import 'package:geosector_app/app.dart'; // Pour accéder aux instances globales
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
// Pour accéder aux instances globales
import 'package:geosector_app/presentation/widgets/passages/passages_list_widget.dart';
import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:geosector_app/core/data/models/passage_model.dart';
@@ -12,6 +14,14 @@ class UserHistoryPage extends StatefulWidget {
State<UserHistoryPage> createState() => _UserHistoryPageState();
}
// Enum pour gérer les types de tri
enum PassageSortType {
dateDesc, // Plus récent en premier (défaut)
dateAsc, // Plus ancien en premier
addressAsc, // Adresse A-Z
addressDesc, // Adresse Z-A
}
class _UserHistoryPageState extends State<UserHistoryPage> {
// Liste qui contiendra les passages convertis
List<Map<String, dynamic>> _convertedPassages = [];
@@ -19,6 +29,13 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
// Variables pour indiquer l'état de chargement
bool _isLoading = true;
String _errorMessage = '';
// Statistiques pour l'affichage
int _totalSectors = 0;
int _sharedMembersCount = 0;
// État du tri actuel
PassageSortType _currentSort = PassageSortType.dateDesc;
@override
void initState() {
@@ -42,21 +59,10 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
debugPrint('Nombre total de passages dans la box: ${allPassages.length}');
// Filtrer pour exclure les passages de type 2
List<PassageModel> filtered = [];
for (var passage in allPassages) {
try {
if (passage.fkType != 2) {
filtered.add(passage);
}
} catch (e) {
debugPrint('Erreur lors du filtrage du passage: $e');
// Si nous ne pouvons pas accéder à fkType, ne pas ajouter ce passage
}
}
// Ne plus filtrer les passages de type 2 - laisser le widget gérer le filtrage
List<PassageModel> filtered = allPassages;
debugPrint(
'Nombre de passages après filtrage (fkType != 2): ${filtered.length}');
debugPrint('Nombre total de passages disponibles: ${filtered.length}');
// Afficher la distribution des types de passages pour le débogage
final Map<int, int> typeCount = {};
@@ -156,9 +162,34 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
debugPrint('Premier passage: ${firstDate.toString()}');
debugPrint('Dernier passage: ${lastDate.toString()}');
}
// Calculer le nombre de secteurs uniques
final Set<int> uniqueSectors = {};
for (var passage in filtered) {
if (passage.fkSector != null && passage.fkSector! > 0) {
uniqueSectors.add(passage.fkSector!);
}
}
// Compter les membres partagés (autres membres dans la même amicale)
int sharedMembers = 0;
try {
// Utiliser l'instance globale définie dans app.dart
final currentUserId = userRepository.getCurrentUser()?.id;
final allMembers = membreRepository.membres; // Utiliser la propriété membres
// Compter les membres autres que l'utilisateur courant
sharedMembers = allMembers.where((membre) => membre.id != currentUserId).length;
debugPrint('Nombre de membres partagés: $sharedMembers');
} catch (e) {
debugPrint('Erreur lors du comptage des membres: $e');
}
setState(() {
_convertedPassages = passagesMap;
_totalSectors = uniqueSectors.length;
_sharedMembersCount = sharedMembers;
_isLoading = false;
});
} catch (e) {
@@ -207,13 +238,13 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
int type;
try {
type = passage.fkType;
// Si le type n'est pas dans les types connus, utiliser 0 comme valeur par défaut
// Si le type n'est pas dans les types connus, utiliser 1 comme valeur par défaut
if (!AppKeys.typesPassages.containsKey(type)) {
type = 0; // Type inconnu
type = 1; // Type 1 par défaut (Effectué)
}
} catch (e) {
debugPrint('Erreur lors de la récupération du type: $e');
type = 0;
type = 1; // Type 1 par défaut
}
// Récupérer le type de règlement avec gestion d'erreur
@@ -265,7 +296,7 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
'Conversion passage ID: ${passage.id}, Type: $type, Date: $date');
return {
'id': passage.id.toString(),
'id': passage.id, // Garder l'ID comme int, pas besoin de toString()
'address': address,
'amount': amount,
'date': date,
@@ -276,6 +307,11 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
'hasReceipt': hasReceipt,
'hasError': hasError,
'fkUser': passage.fkUser, // Ajouter l'ID de l'utilisateur
'isOwnedByCurrentUser': passage.fkUser == userRepository.getCurrentUser()?.id, // Ajout du champ pour le widget
// Ajouter les composants de l'adresse pour le tri
'rue': passage.rue,
'numero': passage.numero,
'rueBis': passage.rueBis,
};
} catch (e) {
debugPrint('ERREUR CRITIQUE lors de la conversion du passage: $e');
@@ -289,17 +325,105 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
'address': 'Adresse non disponible',
'amount': 0.0,
'date': DateTime.now(),
'type': 0,
'payment': 0,
'type': 1, // Type 1 par défaut au lieu de 0
'payment': 1, // Payment 1 par défaut au lieu de 0
'name': 'Nom non disponible',
'notes': '',
'hasReceipt': false,
'hasError': true,
'fkUser': currentUserId, // Ajouter l'ID de l'utilisateur courant
// Composants de l'adresse pour le tri
'rue': '',
'numero': '',
'rueBis': '',
};
}
}
// Méthode pour trier les passages selon le type de tri sélectionné
List<Map<String, dynamic>> _sortPassages(List<Map<String, dynamic>> passages) {
final sortedPassages = List<Map<String, dynamic>>.from(passages);
switch (_currentSort) {
case PassageSortType.dateDesc:
sortedPassages.sort((a, b) {
try {
return (b['date'] as DateTime).compareTo(a['date'] as DateTime);
} catch (e) {
return 0;
}
});
break;
case PassageSortType.dateAsc:
sortedPassages.sort((a, b) {
try {
return (a['date'] as DateTime).compareTo(b['date'] as DateTime);
} catch (e) {
return 0;
}
});
break;
case PassageSortType.addressAsc:
sortedPassages.sort((a, b) {
try {
// Tri intelligent par rue, numéro (numérique), rueBis
final String rueA = a['rue'] ?? '';
final String rueB = b['rue'] ?? '';
final String numeroA = a['numero'] ?? '';
final String numeroB = b['numero'] ?? '';
final String rueBisA = a['rueBis'] ?? '';
final String rueBisB = b['rueBis'] ?? '';
// D'abord comparer les rues
int rueCompare = rueA.toLowerCase().compareTo(rueB.toLowerCase());
if (rueCompare != 0) return rueCompare;
// Si les rues sont identiques, comparer les numéros (numériquement)
int numA = int.tryParse(numeroA) ?? 0;
int numB = int.tryParse(numeroB) ?? 0;
int numCompare = numA.compareTo(numB);
if (numCompare != 0) return numCompare;
// Si les numéros sont identiques, comparer les rueBis
return rueBisA.toLowerCase().compareTo(rueBisB.toLowerCase());
} catch (e) {
return 0;
}
});
break;
case PassageSortType.addressDesc:
sortedPassages.sort((a, b) {
try {
// Tri intelligent inversé par rue, numéro (numérique), rueBis
final String rueA = a['rue'] ?? '';
final String rueB = b['rue'] ?? '';
final String numeroA = a['numero'] ?? '';
final String numeroB = b['numero'] ?? '';
final String rueBisA = a['rueBis'] ?? '';
final String rueBisB = b['rueBis'] ?? '';
// D'abord comparer les rues (inversé)
int rueCompare = rueB.toLowerCase().compareTo(rueA.toLowerCase());
if (rueCompare != 0) return rueCompare;
// Si les rues sont identiques, comparer les numéros (inversé numériquement)
int numA = int.tryParse(numeroA) ?? 0;
int numB = int.tryParse(numeroB) ?? 0;
int numCompare = numB.compareTo(numA);
if (numCompare != 0) return numCompare;
// Si les numéros sont identiques, comparer les rueBis (inversé)
return rueBisB.toLowerCase().compareTo(rueBisA.toLowerCase());
} catch (e) {
return 0;
}
});
break;
}
return sortedPassages;
}
// Construire l'adresse complète à partir des composants
String _buildFullAddress(PassageModel passage) {
final List<String> addressParts = [];
@@ -457,20 +581,45 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
// En-tête avec bouton de rafraîchissement
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Historique des passages',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadPassages,
tooltip: 'Rafraîchir',
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_isLoading
? 'Historique des passages'
: 'Historique des ${_convertedPassages.length} passages${_totalSectors > 0 ? ' ($_totalSectors secteur${_totalSectors > 1 ? 's' : ''})' : ''}',
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
if (!_isLoading && _sharedMembersCount > 0)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text(
'Partagés avec $_sharedMembersCount membre${_sharedMembersCount > 1 ? 's' : ''}',
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.7),
fontStyle: FontStyle.italic,
),
),
),
],
),
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadPassages,
tooltip: 'Rafraîchir',
),
],
),
],
),
@@ -510,61 +659,159 @@ class _UserHistoryPageState extends State<UserHistoryPage> {
)
// Utilisation du widget PassagesListWidget pour afficher la liste des passages
else
Column(
children: [
// Stat rapide pour l'utilisateur
if (_convertedPassages.isNotEmpty)
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'${_convertedPassages.length} passages au total (${_convertedPassages.where((p) => (p['date'] as DateTime).isAfter(DateTime(2024, 12, 13))).length} de décembre 2024)',
style: TextStyle(
fontStyle: FontStyle.italic,
color: theme.colorScheme.primary),
Expanded(
child: Container(
color: Colors.transparent,
child: Column(
children: [
// Widget de liste des passages avec ValueListenableBuilder
Expanded(
child: ValueListenableBuilder(
valueListenable: Hive.box<PassageModel>(AppKeys.passagesBoxName).listenable(),
builder: (context, Box<PassageModel> passagesBox, child) {
// Reconvertir les passages à chaque changement
final List<PassageModel> allPassages = passagesBox.values.toList();
// Appliquer le même filtrage et conversion
List<Map<String, dynamic>> passagesMap = [];
for (var passage in allPassages) {
try {
final Map<String, dynamic> passageMap = _convertPassageModelToMap(passage);
passagesMap.add(passageMap);
} catch (e) {
debugPrint('Erreur lors de la conversion du passage en map: $e');
}
}
// Appliquer le tri sélectionné
passagesMap = _sortPassages(passagesMap);
return PassagesListWidget(
showAddButton: true, // Activer le bouton de création
onAddPassage: () async {
// Ouvrir le dialogue de création de passage
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return PassageFormDialog(
title: 'Nouveau passage',
passageRepository: passageRepository,
userRepository: userRepository,
operationRepository: operationRepository,
onSuccess: () {
// Le widget se rafraîchira automatiquement via ValueListenableBuilder
},
);
},
);
},
sortingButtons: Row(
children: [
// Bouton tri par date avec icône calendrier
IconButton(
icon: Icon(
Icons.calendar_today,
size: 20,
color: _currentSort == PassageSortType.dateDesc ||
_currentSort == PassageSortType.dateAsc
? theme.colorScheme.primary
: theme.colorScheme.onSurface.withOpacity(0.6),
),
tooltip: _currentSort == PassageSortType.dateAsc
? 'Tri par date (ancien en premier)'
: 'Tri par date (récent en premier)',
onPressed: () {
setState(() {
if (_currentSort == PassageSortType.dateDesc) {
_currentSort = PassageSortType.dateAsc;
} else {
_currentSort = PassageSortType.dateDesc;
}
});
},
),
// Indicateur de direction pour la date
if (_currentSort == PassageSortType.dateDesc ||
_currentSort == PassageSortType.dateAsc)
Icon(
_currentSort == PassageSortType.dateAsc
? Icons.arrow_upward
: Icons.arrow_downward,
size: 14,
color: theme.colorScheme.primary,
),
const SizedBox(width: 4),
// Bouton tri par adresse avec icône maison
IconButton(
icon: Icon(
Icons.home,
size: 20,
color: _currentSort == PassageSortType.addressDesc ||
_currentSort == PassageSortType.addressAsc
? theme.colorScheme.primary
: theme.colorScheme.onSurface.withOpacity(0.6),
),
tooltip: _currentSort == PassageSortType.addressAsc
? 'Tri par adresse (A-Z)'
: 'Tri par adresse (Z-A)',
onPressed: () {
setState(() {
if (_currentSort == PassageSortType.addressAsc) {
_currentSort = PassageSortType.addressDesc;
} else {
_currentSort = PassageSortType.addressAsc;
}
});
},
),
// Indicateur de direction pour l'adresse
if (_currentSort == PassageSortType.addressDesc ||
_currentSort == PassageSortType.addressAsc)
Icon(
_currentSort == PassageSortType.addressAsc
? Icons.arrow_upward
: Icons.arrow_downward,
size: 14,
color: theme.colorScheme.primary,
),
],
),
passages: passagesMap,
showFilters: true,
showSearch: true,
showActions: true,
initialSearchQuery: _searchQuery,
initialTypeFilter: 'Tous',
initialPaymentFilter: 'Tous',
excludePassageTypes: const [],
filterByUserId: userRepository.getCurrentUser()?.id,
key: const ValueKey('user_passages_list'),
// Le widget gère maintenant le flux conditionnel par défaut
onPassageSelected: null,
onDetailsView: (passage) {
debugPrint('Affichage des détails: ${passage['id']}');
_showPassageDetails(passage);
},
onPassageEdit: (passage) {
debugPrint('Modification du passage: ${passage['id']}');
_editPassage(passage);
},
onReceiptView: (passage) {
debugPrint('Affichage du reçu pour le passage: ${passage['id']}');
_showReceipt(passage);
},
onPassageDelete: (passage) {
// Pas besoin de recharger, le ValueListenableBuilder
// se rafraîchira automatiquement après la suppression
},
);
},
),
),
),
// Widget de liste des passages
Expanded(
child: PassagesListWidget(
passages: _convertedPassages,
showFilters: true,
showSearch: true,
showActions: true,
initialSearchQuery: _searchQuery,
initialTypeFilter:
'Tous', // Toujours commencer avec 'Tous' pour voir tous les types
initialPaymentFilter: 'Tous',
// Exclure les passages de type 2 (À finaliser)
excludePassageTypes: const [2],
// Filtrer par utilisateur courant
filterByUserId: userRepository.getCurrentUser()?.id,
// Désactiver les filtres de date implicites
key: ValueKey(
'passages_list_${DateTime.now().millisecondsSinceEpoch}'),
onPassageSelected: (passage) {
// Action lors de la sélection d'un passage
debugPrint('Passage sélectionné: ${passage['id']}');
_showPassageDetails(passage);
},
onDetailsView: (passage) {
// Action lors de l'affichage des détails
debugPrint('Affichage des détails: ${passage['id']}');
_showPassageDetails(passage);
},
onPassageEdit: (passage) {
// Action lors de la modification d'un passage
debugPrint('Modification du passage: ${passage['id']}');
_editPassage(passage);
},
onReceiptView: (passage) {
// Action lors de la demande d'affichage du reçu
debugPrint(
'Affichage du reçu pour le passage: ${passage['id']}');
_showReceipt(passage);
},
),
],
),
],
),
),
],
),

View File

@@ -10,6 +10,7 @@ import 'package:geosector_app/presentation/widgets/mapbox_map.dart';
import '../../core/constants/app_keys.dart';
import '../../core/data/models/sector_model.dart';
import '../../core/data/models/passage_model.dart';
import '../../presentation/widgets/passage_map_dialog.dart';
// Extension pour ajouter ln2 (logarithme népérien de 2) comme constante
extension MathConstants on math.Random {
@@ -946,173 +947,17 @@ class _UserMapPageState extends State<UserMapPage> {
// Afficher les informations d'un passage lorsqu'on clique dessus
void _showPassageInfo(Map<String, dynamic> passage) {
final PassageModel passageModel = passage['model'] as PassageModel;
final int type = passageModel.fkType;
// Construire l'adresse complète
final String adresse =
'${passageModel.numero}, ${passageModel.rueBis} ${passageModel.rue}';
// Informations sur l'étage, l'appartement et la résidence (si habitat = 2)
String? etageInfo;
String? apptInfo;
String? residenceInfo;
if (passageModel.fkHabitat == 2) {
if (passageModel.niveau.isNotEmpty) {
etageInfo = 'Etage ${passageModel.niveau}';
}
if (passageModel.appt.isNotEmpty) {
apptInfo = 'appt. ${passageModel.appt}';
}
if (passageModel.residence.isNotEmpty) {
residenceInfo = passageModel.residence;
}
}
// Formater la date (uniquement si le type n'est pas 2 et si la date existe)
String dateInfo = '';
if (type != 2 && passageModel.passedAt != null) {
dateInfo = 'Date: ${_formatDate(passageModel.passedAt!)}';
}
// Récupérer le nom du passage (si le type n'est pas 6 - Maison vide)
String? nomInfo;
if (type != 6 && passageModel.name.isNotEmpty) {
nomInfo = passageModel.name;
}
// Récupérer les informations de règlement si le type est 1 (Effectué) ou 5 (Lot)
Widget? reglementInfo;
if (type == 1 || type == 5) {
final int typeReglementId = passageModel.fkTypeReglement;
final String montant = passageModel.montant;
// Récupérer les informations du type de règlement
if (AppKeys.typesReglements.containsKey(typeReglementId)) {
final Map<String, dynamic> typeReglement =
AppKeys.typesReglements[typeReglementId]!;
final String titre = typeReglement['titre'] as String;
final Color couleur = Color(typeReglement['couleur'] as int);
final IconData iconData = typeReglement['icon_data'] as IconData;
reglementInfo = Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Row(
children: [
Icon(iconData, color: couleur, size: 20),
const SizedBox(width: 8),
Text('$titre: $montant',
style:
TextStyle(color: couleur, fontWeight: FontWeight.bold)),
],
),
);
}
}
// Afficher une bulle d'information
showDialog(
context: context,
builder: (context) => AlertDialog(
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 0),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Afficher en premier si le passage n'est pas affecté à un secteur
if (passageModel.fkSector == null) ...[
Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
border: Border.all(color: Colors.red, width: 1),
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
const Icon(Icons.warning, color: Colors.red, size: 20),
const SizedBox(width: 8),
const Expanded(
child: Text(
'Ce passage n\'est plus affecté à un secteur',
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
),
),
],
),
),
],
Text('Adresse: $adresse'),
if (residenceInfo != null) ...[
const SizedBox(height: 4),
Text(residenceInfo)
],
if (etageInfo != null) ...[
const SizedBox(height: 4),
Text(etageInfo)
],
if (apptInfo != null) ...[
const SizedBox(height: 4),
Text(apptInfo)
],
if (dateInfo.isNotEmpty) ...[
const SizedBox(height: 8),
Text(dateInfo)
],
if (nomInfo != null) ...[
const SizedBox(height: 8),
Text('Nom: $nomInfo')
],
if (reglementInfo != null) reglementInfo,
],
),
actionsPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// Bouton d'édition
IconButton(
onPressed: () {
Navigator.of(context).pop();
// Logique pour éditer le passage
debugPrint('Éditer le passage ${passageModel.id}');
},
icon: const Icon(Icons.edit),
color: Colors.blue,
tooltip: 'Modifier',
),
// Bouton de suppression
IconButton(
onPressed: () {
Navigator.of(context).pop();
// Logique pour supprimer le passage
debugPrint('Supprimer le passage ${passageModel.id}');
},
icon: const Icon(Icons.delete),
color: Colors.red,
tooltip: 'Supprimer',
),
],
),
// Bouton de fermeture
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Fermer'),
),
],
),
],
builder: (context) => PassageMapDialog(
passage: passageModel,
isAdmin: false, // L'utilisateur n'est pas admin
onDeleted: () {
// Recharger les passages après suppression
_loadPassages();
},
),
);
}
// Formater une date
String _formatDate(DateTime date) {
return '${date.day.toString().padLeft(2, '0')}/${date.month.toString().padLeft(2, '0')}/${date.year}';
}
}

View File

@@ -60,6 +60,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
bool _chkStripe = false;
bool _chkMdpManuel = false;
bool _chkUsernameManuel = false;
bool _chkUserDeletePass = false;
// Pour l'upload du logo
final ImagePicker _picker = ImagePicker();
@@ -93,6 +94,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
_chkStripe = amicale?.chkStripe ?? false;
_chkMdpManuel = amicale?.chkMdpManuel ?? false;
_chkUsernameManuel = amicale?.chkUsernameManuel ?? false;
_chkUserDeletePass = amicale?.chkUserDeletePass ?? false;
// Note : Le logo sera chargé dynamiquement depuis l'API
}
@@ -152,6 +154,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
'chk_stripe': amicale.chkStripe ? 1 : 0,
'chk_mdp_manuel': amicale.chkMdpManuel ? 1 : 0,
'chk_username_manuel': amicale.chkUsernameManuel ? 1 : 0,
'chk_user_delete_pass': amicale.chkUserDeletePass ? 1 : 0,
};
// Ajouter les champs réservés aux administrateurs si l'utilisateur est admin
@@ -401,6 +404,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
chkStripe: _chkStripe,
chkMdpManuel: _chkMdpManuel,
chkUsernameManuel: _chkUsernameManuel,
chkUserDeletePass: _chkUserDeletePass,
) ??
AmicaleModel(
id: 0, // Sera remplacé par l'API
@@ -424,6 +428,7 @@ class _AmicaleFormState extends State<AmicaleForm> {
chkStripe: _chkStripe,
chkMdpManuel: _chkMdpManuel,
chkUsernameManuel: _chkUsernameManuel,
chkUserDeletePass: _chkUserDeletePass,
);
debugPrint('🔧 AmicaleModel créé: ${amicale.name}');
@@ -1159,6 +1164,20 @@ class _AmicaleFormState extends State<AmicaleForm> {
),
],
),
const SizedBox(height: 8),
// Checkbox pour autoriser les membres à supprimer des passages
_buildCheckboxOption(
label: "Autoriser les membres à supprimer des passages",
value: _chkUserDeletePass,
onChanged: widget.readOnly
? null
: (value) {
setState(() {
_chkUserDeletePass = value!;
});
},
),
const SizedBox(height: 25),
// Boutons Fermer et Enregistrer

View File

@@ -5,10 +5,8 @@ import 'package:geosector_app/core/services/app_info_service.dart';
import 'package:geosector_app/core/services/current_amicale_service.dart';
import 'package:geosector_app/presentation/widgets/connectivity_indicator.dart';
import 'package:geosector_app/presentation/widgets/user_form_dialog.dart';
import 'package:geosector_app/presentation/widgets/passage_form_dialog.dart';
import 'package:geosector_app/core/utils/api_exception.dart';
import 'package:geosector_app/core/services/theme_service.dart';
import 'package:geosector_app/core/constants/app_keys.dart';
import 'package:go_router/go_router.dart';
/// AppBar personnalisée pour les tableaux de bord
@@ -19,12 +17,6 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
/// Le titre de la page actuelle (optionnel)
final String? pageTitle;
/// Indique si le bouton "Nouveau passage" doit être affiché
final bool showNewPassageButton;
/// Callback appelé lorsque le bouton "Nouveau passage" est pressé
final VoidCallback? onNewPassagePressed;
/// Indique si l'utilisateur est un administrateur
final bool isAdmin;
@@ -35,8 +27,6 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
super.key,
required this.title,
this.pageTitle,
this.showNewPassageButton = true,
this.onNewPassagePressed,
this.isAdmin = false,
this.onLogoutPressed,
});
@@ -166,42 +156,6 @@ class DashboardAppBar extends StatelessWidget implements PreferredSizeWidget {
actions.add(const SizedBox(width: 8));
// Ajouter le bouton "Nouveau passage" seulement si l'utilisateur n'est pas admin
if (!isAdmin) {
actions.add(
TextButton.icon(
icon: const Icon(Icons.add_location_alt, color: Colors.white),
label: const Text('Nouveau passage',
style: TextStyle(color: Colors.white)),
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (dialogContext) => PassageFormDialog(
title: 'Nouveau passage',
passageRepository: passageRepository,
userRepository: userRepository,
operationRepository: operationRepository,
onSuccess: () {
// Callback après création du passage
if (onNewPassagePressed != null) {
onNewPassagePressed!();
}
},
),
);
},
style: TextButton.styleFrom(
backgroundColor: Color(AppKeys.typesPassages[1]!['couleur1']
as int), // Vert des passages effectués
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
),
),
);
actions.add(const SizedBox(width: 8));
}
// Ajouter le sélecteur de thème avec confirmation (désactivé temporairement)
// TODO: Réactiver quand le thème sombre sera corrigé
// actions.add(

View File

@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:geosector_app/presentation/widgets/dashboard_app_bar.dart';
import 'package:geosector_app/presentation/widgets/responsive_navigation.dart';
import 'package:geosector_app/app.dart'; // Pour accéder à userRepository
import 'package:geosector_app/core/theme/app_theme.dart'; // Pour les couleurs du thème
import 'dart:math' as math;
/// Layout commun pour les tableaux de bord utilisateur et administrateur
/// Combine DashboardAppBar et ResponsiveNavigation
@@ -23,12 +26,6 @@ class DashboardLayout extends StatelessWidget {
/// Actions supplémentaires à afficher dans l'AppBar
final List<Widget>? additionalActions;
/// Indique si le bouton "Nouveau passage" doit être affiché
final bool showNewPassageButton;
/// Callback appelé lorsque le bouton "Nouveau passage" est pressé
final VoidCallback? onNewPassagePressed;
/// Widgets à afficher en bas de la sidebar
final List<Widget>? sidebarBottomItems;
@@ -46,8 +43,6 @@ class DashboardLayout extends StatelessWidget {
required this.onDestinationSelected,
required this.destinations,
this.additionalActions,
this.showNewPassageButton = true,
this.onNewPassagePressed,
this.sidebarBottomItems,
this.isAdmin = false,
this.onLogoutPressed,
@@ -79,32 +74,57 @@ class DashboardLayout extends StatelessWidget {
);
}
return Scaffold(
backgroundColor: Colors
.transparent, // Fond transparent pour laisser voir le AdminBackground
appBar: DashboardAppBar(
title: title,
pageTitle: destinations[selectedIndex].label,
showNewPassageButton: showNewPassageButton,
onNewPassagePressed: onNewPassagePressed,
isAdmin: isAdmin,
onLogoutPressed: onLogoutPressed,
),
body: ResponsiveNavigation(
title:
title, // Même si le titre n'est pas affiché dans la navigation, il est utilisé pour la cohérence
body: body,
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: destinations,
// Ne pas afficher le bouton "Nouveau passage" dans la navigation car il est déjà dans l'AppBar
showNewPassageButton: false,
onNewPassagePressed: onNewPassagePressed,
sidebarBottomItems: sidebarBottomItems,
isAdmin: isAdmin,
// Ne pas afficher l'AppBar dans la navigation car nous utilisons DashboardAppBar
showAppBar: false,
),
// Déterminer le rôle de l'utilisateur
final currentUser = userRepository.getCurrentUser();
final userRole = currentUser?.role ?? 1;
// Définir les couleurs du gradient selon le rôle
final gradientColors = userRole > 1
? [Colors.white, Colors.red.shade300] // Admin : fond rouge
: [Colors.white, AppTheme.accentColor.withOpacity(0.3)]; // User : fond vert
return Stack(
children: [
// Fond dégradé avec points
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: gradientColors,
),
),
child: CustomPaint(
painter: DotsPainter(),
child: const SizedBox.expand(),
),
),
// Scaffold avec fond transparent
Scaffold(
backgroundColor: Colors.transparent,
appBar: DashboardAppBar(
title: title,
pageTitle: destinations[selectedIndex].label,
isAdmin: isAdmin,
onLogoutPressed: onLogoutPressed,
),
body: ResponsiveNavigation(
title:
title, // Même si le titre n'est pas affiché dans la navigation, il est utilisé pour la cohérence
body: body,
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: destinations,
// Ne pas afficher le bouton "Nouveau passage" dans la navigation
showNewPassageButton: false,
onNewPassagePressed: null,
sidebarBottomItems: sidebarBottomItems,
isAdmin: isAdmin,
// Ne pas afficher l'AppBar dans la navigation car nous utilisons DashboardAppBar
showAppBar: false,
),
),
],
);
} catch (e) {
debugPrint('ERREUR CRITIQUE dans DashboardLayout.build: $e');
@@ -141,3 +161,26 @@ class DashboardLayout extends StatelessWidget {
}
}
}
/// CustomPainter pour dessiner les petits points blancs sur le fond
class DotsPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.5)
..style = PaintingStyle.fill;
final random = math.Random(42); // Seed fixe pour consistance
final numberOfDots = (size.width * size.height) ~/ 1500;
for (int i = 0; i < numberOfDots; i++) {
final x = random.nextDouble() * size.width;
final y = random.nextDouble() * size.height;
final radius = 1.0 + random.nextDouble() * 2.0;
canvas.drawCircle(Offset(x, y), radius, paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -118,6 +118,7 @@ class _ResponsiveNavigationState extends State<ResponsiveNavigation> {
final isDesktop = size.width > 900;
return Scaffold(
backgroundColor: Colors.transparent, // Fond transparent pour voir le gradient
appBar: widget.showAppBar
? AppBar(
title: Text(widget.title),

View File

@@ -3,8 +3,8 @@ FLUTTER_ROOT=/home/pierre/dev/flutter
FLUTTER_APPLICATION_PATH=/home/pierre/dev/geosector/app
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=3.1.4
FLUTTER_BUILD_NUMBER=314
FLUTTER_BUILD_NAME=3.1.6
FLUTTER_BUILD_NUMBER=316
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false

View File

@@ -4,8 +4,8 @@ export "FLUTTER_ROOT=/home/pierre/dev/flutter"
export "FLUTTER_APPLICATION_PATH=/home/pierre/dev/geosector/app"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=3.1.4"
export "FLUTTER_BUILD_NUMBER=314"
export "FLUTTER_BUILD_NAME=3.1.6"
export "FLUTTER_BUILD_NUMBER=316"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"

View File

@@ -1195,10 +1195,10 @@ packages:
dependency: "direct main"
description:
name: syncfusion_flutter_charts
sha256: "620ab07355f2fdb587b0db7e73c33ec2e72d6a54ed299b1b75411025b173d312"
sha256: acec58b24e7d3fe1c50411af52a3bc0cb83f0a9607b42baedc7322846c31ff36
url: "https://pub.dev"
source: hosted
version: "30.2.6"
version: "30.2.6+1"
syncfusion_flutter_core:
dependency: transitive
description:

View File

@@ -1,7 +1,7 @@
name: geosector_app
description: 'GEOSECTOR - Gestion de distribution des calendriers par secteurs géographiques pour les amicales de pompiers'
publish_to: 'none'
version: 3.1.4+314
version: 3.1.6+316
environment:
sdk: '>=3.0.0 <4.0.0'