Refactoring
This commit is contained in:
@@ -0,0 +1,407 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/widgets/unified_card.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/theme/app_text_styles.dart';
|
||||
import '../../../../core/utils/date_formatter.dart';
|
||||
import '../../../../core/utils/currency_formatter.dart';
|
||||
import '../../domain/entities/demande_aide.dart';
|
||||
|
||||
/// Widget de carte pour afficher une demande d'aide
|
||||
///
|
||||
/// Cette carte affiche les informations essentielles d'une demande d'aide
|
||||
/// avec un design cohérent et des interactions tactiles.
|
||||
class DemandeAideCard extends StatelessWidget {
|
||||
final DemandeAide demande;
|
||||
final bool isSelected;
|
||||
final bool isSelectionMode;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final ValueChanged<bool>? onSelectionChanged;
|
||||
|
||||
const DemandeAideCard({
|
||||
super.key,
|
||||
required this.demande,
|
||||
this.isSelected = false,
|
||||
this.isSelectionMode = false,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onSelectionChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UnifiedCard(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: isSelected
|
||||
? Border.all(color: AppColors.primary, width: 2)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 12),
|
||||
_buildContent(),
|
||||
const SizedBox(height: 12),
|
||||
_buildFooter(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
children: [
|
||||
if (isSelectionMode) ...[
|
||||
Checkbox(
|
||||
value: isSelected,
|
||||
onChanged: onSelectionChanged,
|
||||
activeColor: AppColors.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
demande.titre,
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildStatutChip(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.person,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
demande.nomDemandeur,
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
demande.numeroReference,
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (demande.estUrgente) ...[
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.error.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.priority_high,
|
||||
size: 16,
|
||||
color: AppColors.error,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'URGENT',
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: AppColors.error,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
demande.description,
|
||||
style: AppTextStyles.bodyMedium,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
_buildTypeAideChip(),
|
||||
const SizedBox(width: 8),
|
||||
_buildPrioriteChip(),
|
||||
const Spacer(),
|
||||
if (demande.montantDemande != null)
|
||||
Text(
|
||||
CurrencyFormatter.formatCFA(demande.montantDemande!),
|
||||
style: AppTextStyles.titleSmall.copyWith(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter() {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.access_time,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Créée ${DateFormatter.formatRelative(demande.dateCreation)}',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (demande.dateModification != demande.dateCreation) ...[
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'• Modifiée ${DateFormatter.formatRelative(demande.dateModification)}',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
_buildProgressIndicator(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatutChip() {
|
||||
final color = _getStatutColor(demande.statut);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
demande.statut.libelle,
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTypeAideChip() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_getTypeAideIcon(demande.typeAide),
|
||||
size: 14,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
demande.typeAide.libelle,
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrioriteChip() {
|
||||
final color = _getPrioriteColor(demande.priorite);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
_getPrioriteIcon(demande.priorite),
|
||||
size: 14,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
demande.priorite.libelle,
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProgressIndicator() {
|
||||
final progress = demande.pourcentageAvancement;
|
||||
final color = _getProgressColor(progress);
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 60,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.outline,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
child: FractionallySizedBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
widthFactor: progress / 100,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${progress.toInt()}%',
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Color _getStatutColor(StatutAide statut) {
|
||||
switch (statut) {
|
||||
case StatutAide.brouillon:
|
||||
return AppColors.textSecondary;
|
||||
case StatutAide.soumise:
|
||||
return AppColors.warning;
|
||||
case StatutAide.enEvaluation:
|
||||
return AppColors.info;
|
||||
case StatutAide.approuvee:
|
||||
return AppColors.success;
|
||||
case StatutAide.rejetee:
|
||||
return AppColors.error;
|
||||
case StatutAide.enCours:
|
||||
return AppColors.primary;
|
||||
case StatutAide.terminee:
|
||||
return AppColors.success;
|
||||
case StatutAide.versee:
|
||||
return AppColors.success;
|
||||
case StatutAide.livree:
|
||||
return AppColors.success;
|
||||
case StatutAide.annulee:
|
||||
return AppColors.error;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getPrioriteColor(PrioriteAide priorite) {
|
||||
switch (priorite) {
|
||||
case PrioriteAide.basse:
|
||||
return AppColors.success;
|
||||
case PrioriteAide.normale:
|
||||
return AppColors.info;
|
||||
case PrioriteAide.haute:
|
||||
return AppColors.warning;
|
||||
case PrioriteAide.critique:
|
||||
return AppColors.error;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getProgressColor(double progress) {
|
||||
if (progress < 25) return AppColors.error;
|
||||
if (progress < 50) return AppColors.warning;
|
||||
if (progress < 75) return AppColors.info;
|
||||
return AppColors.success;
|
||||
}
|
||||
|
||||
IconData _getTypeAideIcon(TypeAide typeAide) {
|
||||
switch (typeAide) {
|
||||
case TypeAide.aideFinanciereUrgente:
|
||||
return Icons.attach_money;
|
||||
case TypeAide.aideFinanciereMedicale:
|
||||
return Icons.medical_services;
|
||||
case TypeAide.aideFinanciereEducation:
|
||||
return Icons.school;
|
||||
case TypeAide.aideMaterielleVetements:
|
||||
return Icons.checkroom;
|
||||
case TypeAide.aideMaterielleNourriture:
|
||||
return Icons.restaurant;
|
||||
case TypeAide.aideProfessionnelleFormation:
|
||||
return Icons.work;
|
||||
case TypeAide.aideSocialeAccompagnement:
|
||||
return Icons.support;
|
||||
case TypeAide.autre:
|
||||
return Icons.help;
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getPrioriteIcon(PrioriteAide priorite) {
|
||||
switch (priorite) {
|
||||
case PrioriteAide.basse:
|
||||
return Icons.keyboard_arrow_down;
|
||||
case PrioriteAide.normale:
|
||||
return Icons.remove;
|
||||
case PrioriteAide.haute:
|
||||
return Icons.keyboard_arrow_up;
|
||||
case PrioriteAide.critique:
|
||||
return Icons.priority_high;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/widgets/unified_card.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/theme/app_text_styles.dart';
|
||||
import '../../../../core/utils/file_utils.dart';
|
||||
import '../../domain/entities/demande_aide.dart';
|
||||
|
||||
/// Widget pour afficher la section des documents d'une demande d'aide
|
||||
///
|
||||
/// Ce widget affiche tous les documents joints à une demande d'aide
|
||||
/// avec la possibilité de les visualiser et télécharger.
|
||||
class DemandeAideDocumentsSection extends StatelessWidget {
|
||||
final DemandeAide demande;
|
||||
|
||||
const DemandeAideDocumentsSection({
|
||||
super.key,
|
||||
required this.demande,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (demande.piecesJustificatives.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return UnifiedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Documents joints',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${demande.piecesJustificatives.length}',
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...demande.piecesJustificatives.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final document = entry.value;
|
||||
final isLast = index == demande.piecesJustificatives.length - 1;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_buildDocumentCard(context, document),
|
||||
if (!isLast) const SizedBox(height: 8),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentCard(BuildContext context, PieceJustificative document) {
|
||||
final fileExtension = _getFileExtension(document.nomFichier);
|
||||
final fileIcon = _getFileIcon(fileExtension);
|
||||
final fileColor = _getFileColor(fileExtension);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: fileColor.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
fileIcon,
|
||||
color: fileColor,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
document.nomFichier,
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
document.typeDocument.libelle,
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (document.tailleFichier != null) ...[
|
||||
Text(
|
||||
' • ',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatFileSize(document.tailleFichier!),
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (document.description != null && document.description!.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
document.description!,
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => _previewDocument(context, document),
|
||||
icon: const Icon(Icons.visibility),
|
||||
tooltip: 'Aperçu',
|
||||
iconSize: 20,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _downloadDocument(context, document),
|
||||
icon: const Icon(Icons.download),
|
||||
tooltip: 'Télécharger',
|
||||
iconSize: 20,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getFileExtension(String fileName) {
|
||||
final parts = fileName.split('.');
|
||||
return parts.length > 1 ? parts.last.toLowerCase() : '';
|
||||
}
|
||||
|
||||
IconData _getFileIcon(String extension) {
|
||||
switch (extension) {
|
||||
case 'pdf':
|
||||
return Icons.picture_as_pdf;
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return Icons.description;
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
return Icons.table_chart;
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
return Icons.slideshow;
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'bmp':
|
||||
return Icons.image;
|
||||
case 'mp4':
|
||||
case 'avi':
|
||||
case 'mov':
|
||||
case 'wmv':
|
||||
return Icons.video_file;
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'aac':
|
||||
return Icons.audio_file;
|
||||
case 'zip':
|
||||
case 'rar':
|
||||
case '7z':
|
||||
return Icons.archive;
|
||||
case 'txt':
|
||||
return Icons.text_snippet;
|
||||
default:
|
||||
return Icons.insert_drive_file;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getFileColor(String extension) {
|
||||
switch (extension) {
|
||||
case 'pdf':
|
||||
return Colors.red;
|
||||
case 'doc':
|
||||
case 'docx':
|
||||
return Colors.blue;
|
||||
case 'xls':
|
||||
case 'xlsx':
|
||||
return Colors.green;
|
||||
case 'ppt':
|
||||
case 'pptx':
|
||||
return Colors.orange;
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'png':
|
||||
case 'gif':
|
||||
case 'bmp':
|
||||
return Colors.purple;
|
||||
case 'mp4':
|
||||
case 'avi':
|
||||
case 'mov':
|
||||
case 'wmv':
|
||||
return Colors.indigo;
|
||||
case 'mp3':
|
||||
case 'wav':
|
||||
case 'aac':
|
||||
return Colors.teal;
|
||||
case 'zip':
|
||||
case 'rar':
|
||||
case '7z':
|
||||
return Colors.brown;
|
||||
case 'txt':
|
||||
return Colors.grey;
|
||||
default:
|
||||
return AppColors.textSecondary;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatFileSize(int bytes) {
|
||||
if (bytes < 1024) {
|
||||
return '$bytes B';
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
||||
} else if (bytes < 1024 * 1024 * 1024) {
|
||||
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
||||
} else {
|
||||
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
|
||||
}
|
||||
}
|
||||
|
||||
void _previewDocument(BuildContext context, PieceJustificative document) {
|
||||
// Implémenter la prévisualisation du document
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Aperçu du document'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Nom: ${document.nomFichier}'),
|
||||
Text('Type: ${document.typeDocument.libelle}'),
|
||||
if (document.tailleFichier != null)
|
||||
Text('Taille: ${_formatFileSize(document.tailleFichier!)}'),
|
||||
if (document.description != null && document.description!.isNotEmpty)
|
||||
Text('Description: ${document.description}'),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Fonctionnalité de prévisualisation à implémenter'),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Fermer'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
_downloadDocument(context, document);
|
||||
},
|
||||
child: const Text('Télécharger'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _downloadDocument(BuildContext context, PieceJustificative document) {
|
||||
// Implémenter le téléchargement du document
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Téléchargement de ${document.nomFichier}...'),
|
||||
action: SnackBarAction(
|
||||
label: 'Annuler',
|
||||
onPressed: () {
|
||||
// Annuler le téléchargement
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Simuler le téléchargement
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('${document.nomFichier} téléchargé avec succès'),
|
||||
backgroundColor: AppColors.success,
|
||||
action: SnackBarAction(
|
||||
label: 'Ouvrir',
|
||||
textColor: Colors.white,
|
||||
onPressed: () {
|
||||
// Ouvrir le fichier téléchargé
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/widgets/unified_card.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/theme/app_text_styles.dart';
|
||||
import '../../../../core/utils/date_formatter.dart';
|
||||
import '../../../../core/utils/currency_formatter.dart';
|
||||
import '../../domain/entities/demande_aide.dart';
|
||||
|
||||
/// Widget pour afficher la section des évaluations d'une demande d'aide
|
||||
///
|
||||
/// Ce widget affiche toutes les évaluations effectuées sur une demande d'aide
|
||||
/// avec les détails de chaque évaluation.
|
||||
class DemandeAideEvaluationSection extends StatelessWidget {
|
||||
final DemandeAide demande;
|
||||
|
||||
const DemandeAideEvaluationSection({
|
||||
super.key,
|
||||
required this.demande,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (demande.evaluations.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return UnifiedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Évaluations',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primary.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
'${demande.evaluations.length}',
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...demande.evaluations.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final evaluation = entry.value;
|
||||
final isLast = index == demande.evaluations.length - 1;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_buildEvaluationCard(evaluation),
|
||||
if (!isLast) const SizedBox(height: 12),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEvaluationCard(EvaluationAide evaluation) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildEvaluationHeader(evaluation),
|
||||
const SizedBox(height: 12),
|
||||
_buildEvaluationContent(evaluation),
|
||||
if (evaluation.commentaire != null && evaluation.commentaire!.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
_buildCommentaireSection(evaluation.commentaire!),
|
||||
],
|
||||
if (evaluation.criteres.isNotEmpty) ...[
|
||||
const SizedBox(height: 12),
|
||||
_buildCriteresSection(evaluation.criteres),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEvaluationHeader(EvaluationAide evaluation) {
|
||||
final color = _getDecisionColor(evaluation.decision);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: color.withOpacity(0.1),
|
||||
child: Icon(
|
||||
_getDecisionIcon(evaluation.decision),
|
||||
color: color,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
evaluation.nomEvaluateur,
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
evaluation.typeEvaluateur.libelle,
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
evaluation.decision.libelle,
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
DateFormatter.formatShort(evaluation.dateEvaluation),
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEvaluationContent(EvaluationAide evaluation) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (evaluation.noteGlobale != null) ...[
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Note globale:',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildStarRating(evaluation.noteGlobale!),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${evaluation.noteGlobale!.toStringAsFixed(1)}/5',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
if (evaluation.montantRecommande != null) ...[
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.attach_money,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Montant recommandé:',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
CurrencyFormatter.formatCFA(evaluation.montantRecommande!),
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
color: AppColors.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
if (evaluation.prioriteRecommandee != null) ...[
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.priority_high,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Priorité recommandée:',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: _getPrioriteColor(evaluation.prioriteRecommandee!).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
evaluation.prioriteRecommandee!.libelle,
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: _getPrioriteColor(evaluation.prioriteRecommandee!),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCommentaireSection(String commentaire) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.comment,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Commentaire',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
commentaire,
|
||||
style: AppTextStyles.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCriteresSection(Map<String, double> criteres) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.checklist,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Critères d\'évaluation',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...criteres.entries.map((entry) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: AppTextStyles.bodySmall,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildStarRating(entry.value),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${entry.value.toStringAsFixed(1)}/5',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStarRating(double rating) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(5, (index) {
|
||||
final starValue = index + 1;
|
||||
return Icon(
|
||||
starValue <= rating
|
||||
? Icons.star
|
||||
: starValue - 0.5 <= rating
|
||||
? Icons.star_half
|
||||
: Icons.star_border,
|
||||
size: 16,
|
||||
color: AppColors.warning,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getDecisionColor(StatutAide decision) {
|
||||
switch (decision) {
|
||||
case StatutAide.approuvee:
|
||||
return AppColors.success;
|
||||
case StatutAide.rejetee:
|
||||
return AppColors.error;
|
||||
case StatutAide.enEvaluation:
|
||||
return AppColors.info;
|
||||
default:
|
||||
return AppColors.textSecondary;
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getDecisionIcon(StatutAide decision) {
|
||||
switch (decision) {
|
||||
case StatutAide.approuvee:
|
||||
return Icons.check_circle;
|
||||
case StatutAide.rejetee:
|
||||
return Icons.cancel;
|
||||
case StatutAide.enEvaluation:
|
||||
return Icons.rate_review;
|
||||
default:
|
||||
return Icons.help;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getPrioriteColor(PrioriteAide priorite) {
|
||||
switch (priorite) {
|
||||
case PrioriteAide.basse:
|
||||
return AppColors.success;
|
||||
case PrioriteAide.normale:
|
||||
return AppColors.info;
|
||||
case PrioriteAide.haute:
|
||||
return AppColors.warning;
|
||||
case PrioriteAide.critique:
|
||||
return AppColors.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,744 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/widgets/unified_card.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/theme/app_text_styles.dart';
|
||||
import '../../../../core/utils/validators.dart';
|
||||
import '../../domain/entities/demande_aide.dart';
|
||||
|
||||
/// Section du formulaire pour les bénéficiaires
|
||||
class DemandeAideFormBeneficiairesSection extends StatefulWidget {
|
||||
final List<BeneficiaireAide> beneficiaires;
|
||||
final ValueChanged<List<BeneficiaireAide>> onBeneficiairesChanged;
|
||||
|
||||
const DemandeAideFormBeneficiairesSection({
|
||||
super.key,
|
||||
required this.beneficiaires,
|
||||
required this.onBeneficiairesChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DemandeAideFormBeneficiairesSection> createState() => _DemandeAideFormBeneficiairesState();
|
||||
}
|
||||
|
||||
class _DemandeAideFormBeneficiairesState extends State<DemandeAideFormBeneficiairesSection> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UnifiedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Bénéficiaires',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: _ajouterBeneficiaire,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Ajouter'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Ajoutez les personnes qui bénéficieront de cette aide (optionnel)',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (widget.beneficiaires.isEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.people_outline,
|
||||
size: 48,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Aucun bénéficiaire ajouté',
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
...widget.beneficiaires.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final beneficiaire = entry.value;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildBeneficiaireCard(beneficiaire, index),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBeneficiaireCard(BeneficiaireAide beneficiaire, int index) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
backgroundColor: AppColors.primary.withOpacity(0.1),
|
||||
child: Icon(
|
||||
Icons.person,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'${beneficiaire.prenom} ${beneficiaire.nom}',
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (beneficiaire.age != null)
|
||||
Text(
|
||||
'${beneficiaire.age} ans',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _modifierBeneficiaire(index),
|
||||
icon: const Icon(Icons.edit),
|
||||
iconSize: 20,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _supprimerBeneficiaire(index),
|
||||
icon: const Icon(Icons.delete),
|
||||
iconSize: 20,
|
||||
color: AppColors.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _ajouterBeneficiaire() {
|
||||
_showBeneficiaireDialog();
|
||||
}
|
||||
|
||||
void _modifierBeneficiaire(int index) {
|
||||
_showBeneficiaireDialog(beneficiaire: widget.beneficiaires[index], index: index);
|
||||
}
|
||||
|
||||
void _supprimerBeneficiaire(int index) {
|
||||
final nouveauxBeneficiaires = List<BeneficiaireAide>.from(widget.beneficiaires);
|
||||
nouveauxBeneficiaires.removeAt(index);
|
||||
widget.onBeneficiairesChanged(nouveauxBeneficiaires);
|
||||
}
|
||||
|
||||
void _showBeneficiaireDialog({BeneficiaireAide? beneficiaire, int? index}) {
|
||||
final prenomController = TextEditingController(text: beneficiaire?.prenom ?? '');
|
||||
final nomController = TextEditingController(text: beneficiaire?.nom ?? '');
|
||||
final ageController = TextEditingController(text: beneficiaire?.age?.toString() ?? '');
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(beneficiaire == null ? 'Ajouter un bénéficiaire' : 'Modifier le bénéficiaire'),
|
||||
content: Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: prenomController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Prénom *',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: Validators.required,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: nomController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Nom *',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: Validators.required,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: ageController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Âge',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty) {
|
||||
final age = int.tryParse(value);
|
||||
if (age == null || age < 0 || age > 150) {
|
||||
return 'Veuillez saisir un âge valide';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Annuler'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (formKey.currentState!.validate()) {
|
||||
final nouveauBeneficiaire = BeneficiaireAide(
|
||||
prenom: prenomController.text,
|
||||
nom: nomController.text,
|
||||
age: ageController.text.isEmpty ? null : int.parse(ageController.text),
|
||||
);
|
||||
|
||||
final nouveauxBeneficiaires = List<BeneficiaireAide>.from(widget.beneficiaires);
|
||||
if (index != null) {
|
||||
nouveauxBeneficiaires[index] = nouveauBeneficiaire;
|
||||
} else {
|
||||
nouveauxBeneficiaires.add(nouveauBeneficiaire);
|
||||
}
|
||||
|
||||
widget.onBeneficiairesChanged(nouveauxBeneficiaires);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Text(beneficiaire == null ? 'Ajouter' : 'Modifier'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Section du formulaire pour le contact d'urgence
|
||||
class DemandeAideFormContactSection extends StatefulWidget {
|
||||
final ContactUrgence? contactUrgence;
|
||||
final ValueChanged<ContactUrgence?> onContactChanged;
|
||||
|
||||
const DemandeAideFormContactSection({
|
||||
super.key,
|
||||
required this.contactUrgence,
|
||||
required this.onContactChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DemandeAideFormContactSection> createState() => _DemandeAideFormContactSectionState();
|
||||
}
|
||||
|
||||
class _DemandeAideFormContactSectionState extends State<DemandeAideFormContactSection> {
|
||||
final _prenomController = TextEditingController();
|
||||
final _nomController = TextEditingController();
|
||||
final _telephoneController = TextEditingController();
|
||||
final _emailController = TextEditingController();
|
||||
final _relationController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.contactUrgence != null) {
|
||||
_prenomController.text = widget.contactUrgence!.prenom;
|
||||
_nomController.text = widget.contactUrgence!.nom;
|
||||
_telephoneController.text = widget.contactUrgence!.telephone;
|
||||
_emailController.text = widget.contactUrgence!.email ?? '';
|
||||
_relationController.text = widget.contactUrgence!.relation;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_prenomController.dispose();
|
||||
_nomController.dispose();
|
||||
_telephoneController.dispose();
|
||||
_emailController.dispose();
|
||||
_relationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UnifiedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Contact d\'urgence',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Personne à contacter en cas d\'urgence (optionnel)',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _prenomController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Prénom',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: _updateContact,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _nomController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Nom',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: _updateContact,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _telephoneController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Téléphone',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.phone),
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
onChanged: _updateContact,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Email',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.email),
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
onChanged: _updateContact,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _relationController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Relation',
|
||||
hintText: 'Ex: Conjoint, Parent, Ami...',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.family_restroom),
|
||||
),
|
||||
onChanged: _updateContact,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateContact(String value) {
|
||||
if (_prenomController.text.isNotEmpty ||
|
||||
_nomController.text.isNotEmpty ||
|
||||
_telephoneController.text.isNotEmpty ||
|
||||
_emailController.text.isNotEmpty ||
|
||||
_relationController.text.isNotEmpty) {
|
||||
final contact = ContactUrgence(
|
||||
prenom: _prenomController.text,
|
||||
nom: _nomController.text,
|
||||
telephone: _telephoneController.text,
|
||||
email: _emailController.text.isEmpty ? null : _emailController.text,
|
||||
relation: _relationController.text,
|
||||
);
|
||||
widget.onContactChanged(contact);
|
||||
} else {
|
||||
widget.onContactChanged(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Section du formulaire pour la localisation
|
||||
class DemandeAideFormLocalisationSection extends StatefulWidget {
|
||||
final Localisation? localisation;
|
||||
final ValueChanged<Localisation?> onLocalisationChanged;
|
||||
|
||||
const DemandeAideFormLocalisationSection({
|
||||
super.key,
|
||||
required this.localisation,
|
||||
required this.onLocalisationChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DemandeAideFormLocalisationSection> createState() => _DemandeAideFormLocalisationSectionState();
|
||||
}
|
||||
|
||||
class _DemandeAideFormLocalisationSectionState extends State<DemandeAideFormLocalisationSection> {
|
||||
final _adresseController = TextEditingController();
|
||||
final _villeController = TextEditingController();
|
||||
final _codePostalController = TextEditingController();
|
||||
final _paysController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.localisation != null) {
|
||||
_adresseController.text = widget.localisation!.adresse;
|
||||
_villeController.text = widget.localisation!.ville ?? '';
|
||||
_codePostalController.text = widget.localisation!.codePostal ?? '';
|
||||
_paysController.text = widget.localisation!.pays ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_adresseController.dispose();
|
||||
_villeController.dispose();
|
||||
_codePostalController.dispose();
|
||||
_paysController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UnifiedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Localisation',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Lieu où l\'aide sera fournie (optionnel)',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _adresseController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Adresse',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.location_on),
|
||||
),
|
||||
maxLines: 2,
|
||||
onChanged: _updateLocalisation,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _villeController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Ville',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: _updateLocalisation,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _codePostalController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Code postal',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: _updateLocalisation,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: _paysController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Pays',
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.flag),
|
||||
),
|
||||
onChanged: _updateLocalisation,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _utiliserPositionActuelle,
|
||||
icon: const Icon(Icons.my_location),
|
||||
label: const Text('Utiliser ma position actuelle'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _updateLocalisation(String value) {
|
||||
if (_adresseController.text.isNotEmpty ||
|
||||
_villeController.text.isNotEmpty ||
|
||||
_codePostalController.text.isNotEmpty ||
|
||||
_paysController.text.isNotEmpty) {
|
||||
final localisation = Localisation(
|
||||
adresse: _adresseController.text,
|
||||
ville: _villeController.text.isEmpty ? null : _villeController.text,
|
||||
codePostal: _codePostalController.text.isEmpty ? null : _codePostalController.text,
|
||||
pays: _paysController.text.isEmpty ? null : _paysController.text,
|
||||
latitude: widget.localisation?.latitude,
|
||||
longitude: widget.localisation?.longitude,
|
||||
);
|
||||
widget.onLocalisationChanged(localisation);
|
||||
} else {
|
||||
widget.onLocalisationChanged(null);
|
||||
}
|
||||
}
|
||||
|
||||
void _utiliserPositionActuelle() {
|
||||
// Implémenter la géolocalisation
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Fonctionnalité de géolocalisation à implémenter'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Section du formulaire pour les documents
|
||||
class DemandeAideFormDocumentsSection extends StatefulWidget {
|
||||
final List<PieceJustificative> piecesJustificatives;
|
||||
final ValueChanged<List<PieceJustificative>> onDocumentsChanged;
|
||||
|
||||
const DemandeAideFormDocumentsSection({
|
||||
super.key,
|
||||
required this.piecesJustificatives,
|
||||
required this.onDocumentsChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DemandeAideFormDocumentsSection> createState() => _DemandeAideFormDocumentsSectionState();
|
||||
}
|
||||
|
||||
class _DemandeAideFormDocumentsSectionState extends State<DemandeAideFormDocumentsSection> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
UnifiedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Documents justificatifs',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: _ajouterDocument,
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Ajouter'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Ajoutez des documents pour appuyer votre demande (optionnel)',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (widget.piecesJustificatives.isEmpty)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.upload_file,
|
||||
size: 48,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Aucun document ajouté',
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Formats acceptés: PDF, DOC, JPG, PNG',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
...widget.piecesJustificatives.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final document = entry.value;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildDocumentCard(document, index),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDocumentCard(PieceJustificative document, int index) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.insert_drive_file,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
document.nomFichier,
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
document.typeDocument.libelle,
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _supprimerDocument(index),
|
||||
icon: const Icon(Icons.delete),
|
||||
iconSize: 20,
|
||||
color: AppColors.error,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _ajouterDocument() {
|
||||
// Implémenter la sélection de fichier
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Fonctionnalité de sélection de fichier à implémenter'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _supprimerDocument(int index) {
|
||||
final nouveauxDocuments = List<PieceJustificative>.from(widget.piecesJustificatives);
|
||||
nouveauxDocuments.removeAt(index);
|
||||
widget.onDocumentsChanged(nouveauxDocuments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/widgets/unified_card.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/theme/app_text_styles.dart';
|
||||
import '../../../../core/utils/date_formatter.dart';
|
||||
import '../../domain/entities/demande_aide.dart';
|
||||
|
||||
/// Widget de timeline pour afficher l'historique des statuts d'une demande d'aide
|
||||
///
|
||||
/// Ce widget affiche une timeline verticale avec tous les changements de statut
|
||||
/// de la demande d'aide, incluant les dates et les commentaires.
|
||||
class DemandeAideStatusTimeline extends StatelessWidget {
|
||||
final DemandeAide demande;
|
||||
|
||||
const DemandeAideStatusTimeline({
|
||||
super.key,
|
||||
required this.demande,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final historique = _buildHistorique();
|
||||
|
||||
if (historique.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return UnifiedCard(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Historique des statuts',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
...historique.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final item = entry.value;
|
||||
final isLast = index == historique.length - 1;
|
||||
|
||||
return _buildTimelineItem(
|
||||
item: item,
|
||||
isLast: isLast,
|
||||
isActive: index == 0, // Le premier élément est l'état actuel
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<TimelineItem> _buildHistorique() {
|
||||
final items = <TimelineItem>[];
|
||||
|
||||
// Ajouter l'état actuel
|
||||
items.add(TimelineItem(
|
||||
statut: demande.statut,
|
||||
date: demande.dateModification,
|
||||
commentaire: _getStatutDescription(demande.statut),
|
||||
isActuel: true,
|
||||
));
|
||||
|
||||
// Ajouter l'historique depuis les évaluations
|
||||
for (final evaluation in demande.evaluations) {
|
||||
items.add(TimelineItem(
|
||||
statut: evaluation.decision,
|
||||
date: evaluation.dateEvaluation,
|
||||
commentaire: evaluation.commentaire,
|
||||
evaluateur: evaluation.nomEvaluateur,
|
||||
));
|
||||
}
|
||||
|
||||
// Ajouter la création
|
||||
if (demande.dateCreation != demande.dateModification) {
|
||||
items.add(TimelineItem(
|
||||
statut: StatutAide.brouillon,
|
||||
date: demande.dateCreation,
|
||||
commentaire: 'Demande créée',
|
||||
));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
Widget _buildTimelineItem({
|
||||
required TimelineItem item,
|
||||
required bool isLast,
|
||||
required bool isActive,
|
||||
}) {
|
||||
final color = isActive ? _getStatutColor(item.statut) : AppColors.textSecondary;
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Timeline indicator
|
||||
Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? color : AppColors.surface,
|
||||
border: Border.all(
|
||||
color: color,
|
||||
width: isActive ? 3 : 2,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: isActive
|
||||
? Icon(
|
||||
_getStatutIcon(item.statut),
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (!isLast)
|
||||
Container(
|
||||
width: 2,
|
||||
height: 40,
|
||||
color: AppColors.outline,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Content
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.statut.libelle,
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
fontWeight: isActive ? FontWeight.bold : FontWeight.w600,
|
||||
color: isActive ? color : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (item.isActuel)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'ACTUEL',
|
||||
style: AppTextStyles.labelSmall.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
DateFormatter.formatComplete(item.date),
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (item.evaluateur != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.person,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'Par ${item.evaluateur}',
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: AppColors.textSecondary,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
if (item.commentaire != null && item.commentaire!.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.outline),
|
||||
),
|
||||
child: Text(
|
||||
item.commentaire!,
|
||||
style: AppTextStyles.bodySmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Color _getStatutColor(StatutAide statut) {
|
||||
switch (statut) {
|
||||
case StatutAide.brouillon:
|
||||
return AppColors.textSecondary;
|
||||
case StatutAide.soumise:
|
||||
return AppColors.warning;
|
||||
case StatutAide.enEvaluation:
|
||||
return AppColors.info;
|
||||
case StatutAide.approuvee:
|
||||
return AppColors.success;
|
||||
case StatutAide.rejetee:
|
||||
return AppColors.error;
|
||||
case StatutAide.enCours:
|
||||
return AppColors.primary;
|
||||
case StatutAide.terminee:
|
||||
return AppColors.success;
|
||||
case StatutAide.versee:
|
||||
return AppColors.success;
|
||||
case StatutAide.livree:
|
||||
return AppColors.success;
|
||||
case StatutAide.annulee:
|
||||
return AppColors.error;
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getStatutIcon(StatutAide statut) {
|
||||
switch (statut) {
|
||||
case StatutAide.brouillon:
|
||||
return Icons.edit;
|
||||
case StatutAide.soumise:
|
||||
return Icons.send;
|
||||
case StatutAide.enEvaluation:
|
||||
return Icons.rate_review;
|
||||
case StatutAide.approuvee:
|
||||
return Icons.check;
|
||||
case StatutAide.rejetee:
|
||||
return Icons.close;
|
||||
case StatutAide.enCours:
|
||||
return Icons.play_arrow;
|
||||
case StatutAide.terminee:
|
||||
return Icons.done_all;
|
||||
case StatutAide.versee:
|
||||
return Icons.payment;
|
||||
case StatutAide.livree:
|
||||
return Icons.local_shipping;
|
||||
case StatutAide.annulee:
|
||||
return Icons.cancel;
|
||||
}
|
||||
}
|
||||
|
||||
String _getStatutDescription(StatutAide statut) {
|
||||
switch (statut) {
|
||||
case StatutAide.brouillon:
|
||||
return 'Demande en cours de rédaction';
|
||||
case StatutAide.soumise:
|
||||
return 'Demande soumise pour évaluation';
|
||||
case StatutAide.enEvaluation:
|
||||
return 'Demande en cours d\'évaluation';
|
||||
case StatutAide.approuvee:
|
||||
return 'Demande approuvée';
|
||||
case StatutAide.rejetee:
|
||||
return 'Demande rejetée';
|
||||
case StatutAide.enCours:
|
||||
return 'Aide en cours de traitement';
|
||||
case StatutAide.terminee:
|
||||
return 'Aide terminée';
|
||||
case StatutAide.versee:
|
||||
return 'Montant versé';
|
||||
case StatutAide.livree:
|
||||
return 'Aide livrée';
|
||||
case StatutAide.annulee:
|
||||
return 'Demande annulée';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Classe pour représenter un élément de la timeline
|
||||
class TimelineItem {
|
||||
final StatutAide statut;
|
||||
final DateTime date;
|
||||
final String? commentaire;
|
||||
final String? evaluateur;
|
||||
final bool isActuel;
|
||||
|
||||
const TimelineItem({
|
||||
required this.statut,
|
||||
required this.date,
|
||||
this.commentaire,
|
||||
this.evaluateur,
|
||||
this.isActuel = false,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/theme/app_text_styles.dart';
|
||||
import '../../domain/entities/demande_aide.dart';
|
||||
import '../bloc/demandes_aide/demandes_aide_state.dart';
|
||||
|
||||
/// Bottom sheet pour filtrer les demandes d'aide
|
||||
///
|
||||
/// Permet à l'utilisateur de sélectionner différents critères
|
||||
/// de filtrage pour affiner la liste des demandes d'aide.
|
||||
class DemandesAideFilterBottomSheet extends StatefulWidget {
|
||||
final FiltresDemandesAide filtresActuels;
|
||||
final ValueChanged<FiltresDemandesAide> onFiltresChanged;
|
||||
|
||||
const DemandesAideFilterBottomSheet({
|
||||
super.key,
|
||||
required this.filtresActuels,
|
||||
required this.onFiltresChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DemandesAideFilterBottomSheet> createState() => _DemandesAideFilterBottomSheetState();
|
||||
}
|
||||
|
||||
class _DemandesAideFilterBottomSheetState extends State<DemandesAideFilterBottomSheet> {
|
||||
late FiltresDemandesAide _filtres;
|
||||
final TextEditingController _motCleController = TextEditingController();
|
||||
final TextEditingController _montantMinController = TextEditingController();
|
||||
final TextEditingController _montantMaxController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_filtres = widget.filtresActuels;
|
||||
_motCleController.text = _filtres.motCle ?? '';
|
||||
_montantMinController.text = _filtres.montantMin?.toInt().toString() ?? '';
|
||||
_montantMaxController.text = _filtres.montantMax?.toInt().toString() ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_motCleController.dispose();
|
||||
_montantMinController.dispose();
|
||||
_montantMaxController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height * 0.8,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 16),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildMotCleSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildTypeAideSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildStatutSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildPrioriteSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildUrgenteSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildMontantSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildDateSection(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildActions(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
'Filtrer les demandes',
|
||||
style: AppTextStyles.titleLarge.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMotCleSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Recherche par mot-clé',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _motCleController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Titre, description, demandeur...',
|
||||
prefixIcon: Icon(Icons.search),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(motCle: value.isEmpty ? null : value);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTypeAideSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Type d\'aide',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildFilterChip(
|
||||
label: 'Tous',
|
||||
isSelected: _filtres.typeAide == null,
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(typeAide: null);
|
||||
});
|
||||
},
|
||||
),
|
||||
...TypeAide.values.map((type) => _buildFilterChip(
|
||||
label: type.libelle,
|
||||
isSelected: _filtres.typeAide == type,
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(typeAide: type);
|
||||
});
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatutSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Statut',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildFilterChip(
|
||||
label: 'Tous',
|
||||
isSelected: _filtres.statut == null,
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(statut: null);
|
||||
});
|
||||
},
|
||||
),
|
||||
...StatutAide.values.map((statut) => _buildFilterChip(
|
||||
label: statut.libelle,
|
||||
isSelected: _filtres.statut == statut,
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(statut: statut);
|
||||
});
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPrioriteSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Priorité',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_buildFilterChip(
|
||||
label: 'Toutes',
|
||||
isSelected: _filtres.priorite == null,
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(priorite: null);
|
||||
});
|
||||
},
|
||||
),
|
||||
...PrioriteAide.values.map((priorite) => _buildFilterChip(
|
||||
label: priorite.libelle,
|
||||
isSelected: _filtres.priorite == priorite,
|
||||
onSelected: () {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(priorite: priorite);
|
||||
});
|
||||
},
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUrgenteSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Urgence',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CheckboxListTile(
|
||||
title: const Text('Demandes urgentes uniquement'),
|
||||
value: _filtres.urgente == true,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(urgente: value == true ? true : null);
|
||||
});
|
||||
},
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMontantSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Montant demandé (FCFA)',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _montantMinController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Minimum',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: (value) {
|
||||
final montant = double.tryParse(value);
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(montantMin: montant);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _montantMaxController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Maximum',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
onChanged: (value) {
|
||||
final montant = double.tryParse(value);
|
||||
setState(() {
|
||||
_filtres = _filtres.copyWith(montantMax: montant);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDateSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Période de création',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _selectDate(context, true),
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
label: Text(
|
||||
_filtres.dateDebutCreation != null
|
||||
? '${_filtres.dateDebutCreation!.day}/${_filtres.dateDebutCreation!.month}/${_filtres.dateDebutCreation!.year}'
|
||||
: 'Date début',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () => _selectDate(context, false),
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
label: Text(
|
||||
_filtres.dateFinCreation != null
|
||||
? '${_filtres.dateFinCreation!.day}/${_filtres.dateFinCreation!.month}/${_filtres.dateFinCreation!.year}'
|
||||
: 'Date fin',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterChip({
|
||||
required String label,
|
||||
required bool isSelected,
|
||||
required VoidCallback onSelected,
|
||||
}) {
|
||||
return FilterChip(
|
||||
label: Text(label),
|
||||
selected: isSelected,
|
||||
onSelected: (_) => onSelected(),
|
||||
selectedColor: AppColors.primary.withOpacity(0.2),
|
||||
checkmarkColor: AppColors.primary,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActions() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _reinitialiserFiltres,
|
||||
child: const Text('Réinitialiser'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: _appliquerFiltres,
|
||||
child: Text('Appliquer (${_filtres.nombreFiltresActifs})'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectDate(BuildContext context, bool isStartDate) async {
|
||||
final DateTime? picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: isStartDate
|
||||
? _filtres.dateDebutCreation ?? DateTime.now()
|
||||
: _filtres.dateFinCreation ?? DateTime.now(),
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
|
||||
if (picked != null) {
|
||||
setState(() {
|
||||
if (isStartDate) {
|
||||
_filtres = _filtres.copyWith(dateDebutCreation: picked);
|
||||
} else {
|
||||
_filtres = _filtres.copyWith(dateFinCreation: picked);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _reinitialiserFiltres() {
|
||||
setState(() {
|
||||
_filtres = const FiltresDemandesAide();
|
||||
_motCleController.clear();
|
||||
_montantMinController.clear();
|
||||
_montantMaxController.clear();
|
||||
});
|
||||
}
|
||||
|
||||
void _appliquerFiltres() {
|
||||
widget.onFiltresChanged(_filtres);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/theme/app_text_styles.dart';
|
||||
import '../bloc/demandes_aide/demandes_aide_event.dart';
|
||||
|
||||
/// Bottom sheet pour trier les demandes d'aide
|
||||
///
|
||||
/// Permet à l'utilisateur de sélectionner un critère de tri
|
||||
/// et l'ordre (croissant/décroissant) pour la liste des demandes.
|
||||
class DemandesAideSortBottomSheet extends StatefulWidget {
|
||||
final TriDemandes? critereActuel;
|
||||
final bool croissantActuel;
|
||||
final Function(TriDemandes critere, bool croissant) onTriChanged;
|
||||
|
||||
const DemandesAideSortBottomSheet({
|
||||
super.key,
|
||||
this.critereActuel,
|
||||
required this.croissantActuel,
|
||||
required this.onTriChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DemandesAideSortBottomSheet> createState() => _DemandesAideSortBottomSheetState();
|
||||
}
|
||||
|
||||
class _DemandesAideSortBottomSheetState extends State<DemandesAideSortBottomSheet> {
|
||||
late TriDemandes? _critereSelectionne;
|
||||
late bool _croissant;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_critereSelectionne = widget.critereActuel;
|
||||
_croissant = widget.croissantActuel;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const SizedBox(height: 16),
|
||||
_buildCriteresList(),
|
||||
const SizedBox(height: 16),
|
||||
_buildOrdreSection(),
|
||||
const SizedBox(height: 24),
|
||||
_buildActions(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
'Trier les demandes',
|
||||
style: AppTextStyles.titleLarge.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCriteresList() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Critère de tri',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...TriDemandes.values.map((critere) => _buildCritereItem(critere)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCritereItem(TriDemandes critere) {
|
||||
final isSelected = _critereSelectionne == critere;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
elevation: isSelected ? 2 : 0,
|
||||
color: isSelected ? AppColors.primary.withOpacity(0.1) : null,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
_getCritereIcon(critere),
|
||||
color: isSelected ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
title: Text(
|
||||
critere.libelle,
|
||||
style: AppTextStyles.bodyLarge.copyWith(
|
||||
color: isSelected ? AppColors.primary : AppColors.textPrimary,
|
||||
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
_getCritereDescription(critere),
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: isSelected ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
trailing: isSelected
|
||||
? Icon(
|
||||
Icons.check_circle,
|
||||
color: AppColors.primary,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_critereSelectionne = critere;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOrdreSection() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Ordre de tri',
|
||||
style: AppTextStyles.titleMedium.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Card(
|
||||
elevation: _croissant ? 2 : 0,
|
||||
color: _croissant ? AppColors.primary.withOpacity(0.1) : null,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
Icons.arrow_upward,
|
||||
color: _croissant ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
title: Text(
|
||||
'Croissant',
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
color: _croissant ? AppColors.primary : AppColors.textPrimary,
|
||||
fontWeight: _croissant ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
_getOrdreDescription(true),
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: _croissant ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
trailing: _croissant
|
||||
? Icon(
|
||||
Icons.check_circle,
|
||||
color: AppColors.primary,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_croissant = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Card(
|
||||
elevation: !_croissant ? 2 : 0,
|
||||
color: !_croissant ? AppColors.primary.withOpacity(0.1) : null,
|
||||
child: ListTile(
|
||||
leading: Icon(
|
||||
Icons.arrow_downward,
|
||||
color: !_croissant ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
title: Text(
|
||||
'Décroissant',
|
||||
style: AppTextStyles.bodyMedium.copyWith(
|
||||
color: !_croissant ? AppColors.primary : AppColors.textPrimary,
|
||||
fontWeight: !_croissant ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
_getOrdreDescription(false),
|
||||
style: AppTextStyles.bodySmall.copyWith(
|
||||
color: !_croissant ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
trailing: !_croissant
|
||||
? Icon(
|
||||
Icons.check_circle,
|
||||
color: AppColors.primary,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_croissant = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActions() {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _reinitialiserTri,
|
||||
child: const Text('Réinitialiser'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: _critereSelectionne != null ? _appliquerTri : null,
|
||||
child: const Text('Appliquer'),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getCritereIcon(TriDemandes critere) {
|
||||
switch (critere) {
|
||||
case TriDemandes.dateCreation:
|
||||
return Icons.calendar_today;
|
||||
case TriDemandes.dateModification:
|
||||
return Icons.update;
|
||||
case TriDemandes.titre:
|
||||
return Icons.title;
|
||||
case TriDemandes.statut:
|
||||
return Icons.flag;
|
||||
case TriDemandes.priorite:
|
||||
return Icons.priority_high;
|
||||
case TriDemandes.montant:
|
||||
return Icons.attach_money;
|
||||
case TriDemandes.demandeur:
|
||||
return Icons.person;
|
||||
}
|
||||
}
|
||||
|
||||
String _getCritereDescription(TriDemandes critere) {
|
||||
switch (critere) {
|
||||
case TriDemandes.dateCreation:
|
||||
return 'Trier par date de création de la demande';
|
||||
case TriDemandes.dateModification:
|
||||
return 'Trier par date de dernière modification';
|
||||
case TriDemandes.titre:
|
||||
return 'Trier par titre de la demande (alphabétique)';
|
||||
case TriDemandes.statut:
|
||||
return 'Trier par statut de la demande';
|
||||
case TriDemandes.priorite:
|
||||
return 'Trier par niveau de priorité';
|
||||
case TriDemandes.montant:
|
||||
return 'Trier par montant demandé';
|
||||
case TriDemandes.demandeur:
|
||||
return 'Trier par nom du demandeur (alphabétique)';
|
||||
}
|
||||
}
|
||||
|
||||
String _getOrdreDescription(bool croissant) {
|
||||
if (_critereSelectionne == null) return '';
|
||||
|
||||
switch (_critereSelectionne!) {
|
||||
case TriDemandes.dateCreation:
|
||||
case TriDemandes.dateModification:
|
||||
return croissant ? 'Plus ancien en premier' : 'Plus récent en premier';
|
||||
case TriDemandes.titre:
|
||||
case TriDemandes.demandeur:
|
||||
return croissant ? 'A à Z' : 'Z à A';
|
||||
case TriDemandes.statut:
|
||||
return croissant ? 'Brouillon à Terminée' : 'Terminée à Brouillon';
|
||||
case TriDemandes.priorite:
|
||||
return croissant ? 'Basse à Critique' : 'Critique à Basse';
|
||||
case TriDemandes.montant:
|
||||
return croissant ? 'Montant le plus faible' : 'Montant le plus élevé';
|
||||
}
|
||||
}
|
||||
|
||||
void _reinitialiserTri() {
|
||||
setState(() {
|
||||
_critereSelectionne = null;
|
||||
_croissant = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _appliquerTri() {
|
||||
if (_critereSelectionne != null) {
|
||||
widget.onTriChanged(_critereSelectionne!, _croissant);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user