style: redesign equipment list cards to improve spacing and center-align the availability badge
This commit is contained in:
@@ -525,78 +525,123 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
|
|||||||
|
|
||||||
// ✅ RepaintBoundary pour isoler le repaint de chaque carte
|
// ✅ RepaintBoundary pour isoler le repaint de chaque carte
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
key: ValueKey(equipment.id),
|
key: ValueKey(equipment.id),
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
color: isSelectionMode && isSelected
|
elevation: 1,
|
||||||
? AppColors.rouge.withValues(alpha: 0.1)
|
shape: RoundedRectangleBorder(
|
||||||
: null,
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: ListTile(
|
side: BorderSide(
|
||||||
leading: isSelectionMode
|
color: isSelectionMode && isSelected
|
||||||
? Checkbox(
|
? AppColors.rouge
|
||||||
value: isSelected,
|
: Colors.grey.shade200,
|
||||||
onChanged: (value) => toggleItemSelection(equipment.id),
|
width: isSelectionMode && isSelected ? 2 : 1,
|
||||||
activeColor: AppColors.rouge,
|
),
|
||||||
|
),
|
||||||
|
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(
|
else
|
||||||
backgroundColor:
|
Padding(
|
||||||
equipment.category.color.withValues(alpha: 0.2),
|
padding: const EdgeInsets.only(right: 16),
|
||||||
child: equipment.category.getIcon(
|
child: Container(
|
||||||
size: 20,
|
width: 44,
|
||||||
color: equipment.category.color,
|
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(
|
Expanded(
|
||||||
child: Text(
|
child: Column(
|
||||||
equipment.id,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
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 &&
|
if (equipment.category != EquipmentCategory.consumable &&
|
||||||
equipment.category != EquipmentCategory.cable)
|
equipment.category != EquipmentCategory.cable)
|
||||||
EquipmentStatusBadge(equipment: equipment),
|
Padding(
|
||||||
],
|
padding: const EdgeInsets.only(right: 16),
|
||||||
),
|
child: 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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
|
||||||
// Afficher la quantité disponible pour les consommables/câbles
|
// 4. Trailing Action Buttons
|
||||||
if (equipment.category == EquipmentCategory.consumable ||
|
if (!isSelectionMode)
|
||||||
equipment.category == EquipmentCategory.cable) ...[
|
Row(
|
||||||
const SizedBox(height: 4),
|
|
||||||
_buildQuantityDisplay(equipment),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: isSelectionMode
|
|
||||||
? null
|
|
||||||
: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// Bouton Restock (uniquement pour consommables/câbles avec permission)
|
// Bouton Restock (uniquement pour consommables/câbles avec permission)
|
||||||
@@ -606,46 +651,60 @@ class _EquipmentManagementPageState extends State<EquipmentManagementPage>
|
|||||||
requiredPermissions: const ['manage_equipment'],
|
requiredPermissions: const ['manage_equipment'],
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.add_shopping_cart,
|
icon: const Icon(Icons.add_shopping_cart,
|
||||||
color: AppColors.rouge),
|
color: AppColors.rouge, size: 20),
|
||||||
tooltip: 'Restock',
|
tooltip: 'Restock',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
onPressed: () => _showRestockDialog(equipment),
|
onPressed: () => _showRestockDialog(equipment),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (equipment.category == EquipmentCategory.consumable ||
|
||||||
|
equipment.category == EquipmentCategory.cable)
|
||||||
|
const SizedBox(width: 8),
|
||||||
// Bouton QR Code
|
// Bouton QR Code
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.qr_code, color: AppColors.rouge),
|
icon: const Icon(Icons.qr_code, color: AppColors.rouge, size: 20),
|
||||||
tooltip: 'QR Code',
|
tooltip: 'QR Code',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
onPressed: () => showDialog(
|
onPressed: () => showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
QRCodeDialog.forEquipment(equipment),
|
QRCodeDialog.forEquipment(equipment),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
// Bouton Modifier (permission required)
|
// Bouton Modifier (permission required)
|
||||||
PermissionGate(
|
PermissionGate(
|
||||||
requiredPermissions: const ['manage_equipment'],
|
requiredPermissions: const ['manage_equipment'],
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.edit, color: AppColors.rouge),
|
icon: const Icon(Icons.edit, color: AppColors.rouge, size: 20),
|
||||||
tooltip: 'Modifier',
|
tooltip: 'Modifier',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
onPressed: () => _editEquipment(equipment),
|
onPressed: () => _editEquipment(equipment),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
// Bouton Supprimer (permission required)
|
// Bouton Supprimer (permission required)
|
||||||
PermissionGate(
|
PermissionGate(
|
||||||
requiredPermissions: const ['manage_equipment'],
|
requiredPermissions: const ['manage_equipment'],
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.delete, color: Colors.red),
|
icon: const Icon(Icons.delete, color: Colors.red, size: 20),
|
||||||
tooltip: 'Supprimer',
|
tooltip: 'Supprimer',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
onPressed: () => _deleteEquipment(equipment),
|
onPressed: () => _deleteEquipment(equipment),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: isSelectionMode
|
],
|
||||||
? () => toggleItemSelection(equipment.id)
|
),
|
||||||
: () => _viewEquipmentDetails(equipment),
|
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildQuantityDisplay(EquipmentModel equipment) {
|
Widget _buildQuantityDisplay(EquipmentModel equipment) {
|
||||||
|
|||||||
Reference in New Issue
Block a user