On release/v3.1.4: Sauvegarde temporaire pour changement de branche
This commit is contained in:
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
Binary file not shown.
@@ -1 +0,0 @@
|
||||
/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/dart_build_result.json:
|
||||
@@ -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"]}
|
||||
@@ -1 +0,0 @@
|
||||
{"dependencies":[],"code_assets":[]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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"]}
|
||||
@@ -1 +0,0 @@
|
||||
{"inputs":[],"outputs":[]}
|
||||
@@ -1 +0,0 @@
|
||||
/home/pierre/dev/geosector/app/.dart_tool/flutter_build/9801dd92544a637fcb18c8ad3c09ddaa/native_assets.json:
|
||||
@@ -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
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"format-version":[1,0,0],"native-assets":{}}
|
||||
@@ -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
Binary file not shown.
@@ -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
@@ -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
@@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
@@ -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)**
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -382,7 +382,6 @@ class _AdminDashboardPageState extends State<AdminDashboardPage> with WidgetsBin
|
||||
});
|
||||
},
|
||||
destinations: destinations,
|
||||
showNewPassageButton: false,
|
||||
isAdmin: true,
|
||||
body: pages[_selectedIndex],
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user