From 854b0a9bb036cf3ba10d77478cf2af03ec1085c0 Mon Sep 17 00:00:00 2001 From: ElPoyo Date: Tue, 26 May 2026 14:54:38 +0200 Subject: [PATCH] style: redesign equipment list cards to improve spacing and center-align the availability badge --- .../lib/views/equipment_management_page.dart | 201 +++++++++++------- 1 file changed, 130 insertions(+), 71 deletions(-) diff --git a/em2rp/lib/views/equipment_management_page.dart b/em2rp/lib/views/equipment_management_page.dart index b5be32c..67ac9c9 100644 --- a/em2rp/lib/views/equipment_management_page.dart +++ b/em2rp/lib/views/equipment_management_page.dart @@ -525,78 +525,123 @@ class _EquipmentManagementPageState extends State // ✅ RepaintBoundary pour isoler le repaint de chaque carte return RepaintBoundary( - key: ValueKey(equipment.id), - child: Card( - margin: const EdgeInsets.only(bottom: 12), - color: isSelectionMode && isSelected - ? AppColors.rouge.withValues(alpha: 0.1) - : null, - child: ListTile( - leading: isSelectionMode - ? Checkbox( - value: isSelected, - onChanged: (value) => toggleItemSelection(equipment.id), - activeColor: AppColors.rouge, + key: ValueKey(equipment.id), + child: Card( + margin: const EdgeInsets.only(bottom: 12), + elevation: 1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: isSelectionMode && isSelected + ? AppColors.rouge + : Colors.grey.shade200, + width: isSelectionMode && isSelected ? 2 : 1, + ), + ), + color: isSelectionMode && isSelected + ? AppColors.rouge.withValues(alpha: 0.05) + : Colors.white, + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: isSelectionMode + ? () => toggleItemSelection(equipment.id) + : () => _viewEquipmentDetails(equipment), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // 1. leading selection or icon + if (isSelectionMode) + Padding( + padding: const EdgeInsets.only(right: 12), + child: Checkbox( + value: isSelected, + onChanged: (value) => toggleItemSelection(equipment.id), + activeColor: AppColors.rouge, + ), ) - : CircleAvatar( - backgroundColor: - equipment.category.color.withValues(alpha: 0.2), - child: equipment.category.getIcon( - size: 20, - color: equipment.category.color, + else + Padding( + padding: const EdgeInsets.only(right: 16), + child: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: equipment.category.color.withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(10), + ), + child: Center( + child: equipment.category.getIcon( + size: 22, + color: equipment.category.color, + ), + ), ), ), - title: Row( - children: [ + + // 2. Info details (ID, Brand/Model, Subcategory/Quantity) Expanded( - child: Text( - equipment.id, - style: const TextStyle(fontWeight: FontWeight.bold), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + equipment.id, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + color: AppColors.noir, + ), + ), + const SizedBox(height: 4), + Text( + '${equipment.brand ?? ''} ${equipment.model ?? ''}' + .trim() + .isNotEmpty + ? '${equipment.brand ?? ''} ${equipment.model ?? ''}'.trim() + : 'Marque/Modèle non défini', + style: TextStyle( + color: Colors.grey[700], + fontSize: 13, + ), + ), + // Sous-catégorie + if (equipment.subCategory != null && + equipment.subCategory!.isNotEmpty) ...[ + const SizedBox(height: 2), + Text( + '📁 ${equipment.subCategory}', + style: TextStyle( + color: Colors.grey[500], + fontSize: 11, + fontStyle: FontStyle.italic, + ), + ), + ], + // Quantité pour consommables + if (equipment.category == EquipmentCategory.consumable || + equipment.category == EquipmentCategory.cable) ...[ + const SizedBox(height: 6), + _buildQuantityDisplay(equipment), + ], + ], ), ), - // Afficher le badge de statut calculé dynamiquement + + const SizedBox(width: 16), + + // 3. Status Badge (centered vertically in the row!) if (equipment.category != EquipmentCategory.consumable && equipment.category != EquipmentCategory.cable) - EquipmentStatusBadge(equipment: equipment), - ], - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text( - '${equipment.brand ?? ''} ${equipment.model ?? ''}' - .trim() - .isNotEmpty - ? '${equipment.brand ?? ''} ${equipment.model ?? ''}' - .trim() - : 'Marque/Modèle non défini', - style: TextStyle(color: Colors.grey[600], fontSize: 14), - ), - // Afficher la sous-catégorie si elle existe - if (equipment.subCategory != null && - equipment.subCategory!.isNotEmpty) ...[ - const SizedBox(height: 2), - Text( - '📁 ${equipment.subCategory}', - style: TextStyle( - color: Colors.grey[500], - fontSize: 12, - fontStyle: FontStyle.italic, - ), + Padding( + padding: const EdgeInsets.only(right: 16), + child: EquipmentStatusBadge(equipment: equipment), ), - ], - // Afficher la quantité disponible pour les consommables/câbles - if (equipment.category == EquipmentCategory.consumable || - equipment.category == EquipmentCategory.cable) ...[ - const SizedBox(height: 4), - _buildQuantityDisplay(equipment), - ], - ], - ), - trailing: isSelectionMode - ? null - : Row( + + // 4. Trailing Action Buttons + if (!isSelectionMode) + Row( mainAxisSize: MainAxisSize.min, children: [ // Bouton Restock (uniquement pour consommables/câbles avec permission) @@ -606,46 +651,60 @@ class _EquipmentManagementPageState extends State requiredPermissions: const ['manage_equipment'], child: IconButton( icon: const Icon(Icons.add_shopping_cart, - color: AppColors.rouge), + color: AppColors.rouge, size: 20), tooltip: 'Restock', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), onPressed: () => _showRestockDialog(equipment), ), ), + if (equipment.category == EquipmentCategory.consumable || + equipment.category == EquipmentCategory.cable) + const SizedBox(width: 8), // Bouton QR Code IconButton( - icon: const Icon(Icons.qr_code, color: AppColors.rouge), + icon: const Icon(Icons.qr_code, color: AppColors.rouge, size: 20), tooltip: 'QR Code', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), onPressed: () => showDialog( context: context, builder: (context) => QRCodeDialog.forEquipment(equipment), ), ), + const SizedBox(width: 8), // Bouton Modifier (permission required) PermissionGate( requiredPermissions: const ['manage_equipment'], child: IconButton( - icon: const Icon(Icons.edit, color: AppColors.rouge), + icon: const Icon(Icons.edit, color: AppColors.rouge, size: 20), tooltip: 'Modifier', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), onPressed: () => _editEquipment(equipment), ), ), + const SizedBox(width: 8), // Bouton Supprimer (permission required) PermissionGate( requiredPermissions: const ['manage_equipment'], child: IconButton( - icon: const Icon(Icons.delete, color: Colors.red), + icon: const Icon(Icons.delete, color: Colors.red, size: 20), tooltip: 'Supprimer', + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), onPressed: () => _deleteEquipment(equipment), ), ), ], ), - onTap: isSelectionMode - ? () => toggleItemSelection(equipment.id) - : () => _viewEquipmentDetails(equipment), + ], + ), ), - )); + ), + ), + ); } Widget _buildQuantityDisplay(EquipmentModel equipment) {