Refactor de la page `equipment_detail_page` en la décomposant en plusieurs widgets de section réutilisables pour une meilleure lisibilité et maintenance : - `EquipmentHeaderSection` : En-tête avec titre et informations principales. - `EquipmentMainInfoSection` : Informations sur la catégorie, la marque, le modèle et le statut. - `EquipmentNotesSection` : Affichage des notes. - `EquipmentDatesSection` : Gestion de l'affichage des dates (achat, maintenance, création, etc.). - `EquipmentPriceSection` : Section dédiée aux prix. - `EquipmentMaintenanceHistorySection` : Historique des maintenances. - `EquipmentAssociatedEventsSection` : Placeholder pour les événements à venir. - `EquipmentReferencingContainers` : Affiche les boites (containers) qui contiennent cet équipement. Ajout de plusieurs widgets communs et utilitaires : - Widgets UI : `SearchBarWidget`, `SelectionAppBar`, `CustomFilterChip`, `EmptyState`, `InfoChip`, `StatusBadge`, `QuantityDisplay`. - Dialogues : `RestockDialog` pour les consommables et `DialogUtils` pour les confirmations génériques. Autres modifications : - Mise à jour de la terminologie "Container" en "Boite" dans l'interface utilisateur. - Amélioration de la sélection d'équipements dans le formulaire des boites. - Ajout d'instructions pour Copilot (`copilot-instructions.md`). - Mise à jour de certaines icônes pour les types de boites.
289 lines
8.9 KiB
Dart
289 lines
8.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:em2rp/models/container_model.dart';
|
|
import 'package:em2rp/models/equipment_model.dart';
|
|
import 'package:em2rp/services/container_service.dart';
|
|
import 'package:em2rp/utils/colors.dart';
|
|
import 'package:em2rp/views/container_detail_page.dart';
|
|
|
|
/// Widget pour afficher les containers qui référencent un équipement
|
|
class EquipmentReferencingContainers extends StatefulWidget {
|
|
final String equipmentId;
|
|
|
|
const EquipmentReferencingContainers({
|
|
super.key,
|
|
required this.equipmentId,
|
|
});
|
|
|
|
@override
|
|
State<EquipmentReferencingContainers> createState() => _EquipmentReferencingContainersState();
|
|
}
|
|
|
|
class _EquipmentReferencingContainersState extends State<EquipmentReferencingContainers> {
|
|
final ContainerService _containerService = ContainerService();
|
|
List<ContainerModel> _referencingContainers = [];
|
|
bool _isLoading = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadReferencingContainers();
|
|
}
|
|
|
|
Future<void> _loadReferencingContainers() async {
|
|
try {
|
|
final containers = await _containerService.findContainersWithEquipment(widget.equipmentId);
|
|
setState(() {
|
|
_referencingContainers = containers;
|
|
_isLoading = false;
|
|
});
|
|
} catch (e) {
|
|
setState(() {
|
|
_isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_referencingContainers.isEmpty && !_isLoading) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return Card(
|
|
elevation: 2,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(Icons.inventory_2, color: AppColors.rouge),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
'Containers contenant cet équipement',
|
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const Divider(height: 24),
|
|
if (_isLoading)
|
|
const Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
else if (_referencingContainers.isEmpty)
|
|
const Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child: Center(
|
|
child: Text(
|
|
'Cet équipement n\'est dans aucun container',
|
|
style: TextStyle(color: Colors.grey),
|
|
),
|
|
),
|
|
)
|
|
else
|
|
_buildContainersGrid(),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildContainersGrid() {
|
|
final screenWidth = MediaQuery.of(context).size.width;
|
|
final isMobile = screenWidth < 800;
|
|
final isTablet = screenWidth < 1200;
|
|
|
|
final crossAxisCount = isMobile ? 1 : (isTablet ? 2 : 3);
|
|
|
|
return GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: crossAxisCount,
|
|
crossAxisSpacing: 12,
|
|
mainAxisSpacing: 8,
|
|
childAspectRatio: 7.5,
|
|
),
|
|
itemCount: _referencingContainers.length,
|
|
itemBuilder: (context, index) {
|
|
final container = _referencingContainers[index];
|
|
return _buildContainerCard(container);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildContainerCard(ContainerModel container) {
|
|
return Card(
|
|
elevation: 1,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: InkWell(
|
|
onTap: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (context) => ContainerDetailPage(container: container),
|
|
),
|
|
);
|
|
},
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
child: Row(
|
|
children: [
|
|
// Icône du type de container
|
|
container.type.getIcon(size: 28, color: AppColors.rouge),
|
|
const SizedBox(width: 10),
|
|
// Infos textuelles
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
container.id,
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.bold,
|
|
height: 1.0,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
if (container.notes != null && container.notes!.isNotEmpty)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 2),
|
|
child: Text(
|
|
container.notes!,
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
color: Colors.grey[600],
|
|
height: 1.0,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
)
|
|
else
|
|
Text(
|
|
container.name,
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color: Colors.grey[600],
|
|
height: 1.0,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
// Badges compacts
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
_buildStatusBadge(_getStatusLabel(container.status), _getStatusColor(container.status)),
|
|
if (container.itemCount > 0)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 2),
|
|
child: _buildCountBadge(container.itemCount),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusBadge(String label, Color color) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: color.withValues(alpha: 0.15),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: color.withValues(alpha: 0.4), width: 0.5),
|
|
),
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 9,
|
|
color: color,
|
|
fontWeight: FontWeight.bold,
|
|
height: 1.0,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCountBadge(int count) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.withValues(alpha: 0.15),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: Colors.green.withValues(alpha: 0.4), width: 0.5),
|
|
),
|
|
child: Text(
|
|
'$count article${count > 1 ? 's' : ''}',
|
|
style: const TextStyle(
|
|
fontSize: 9,
|
|
color: Colors.green,
|
|
fontWeight: FontWeight.bold,
|
|
height: 1.0,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getStatusLabel(EquipmentStatus status) {
|
|
switch (status) {
|
|
case EquipmentStatus.available:
|
|
return 'Disponible';
|
|
case EquipmentStatus.inUse:
|
|
return 'En prestation';
|
|
case EquipmentStatus.rented:
|
|
return 'Loué';
|
|
case EquipmentStatus.lost:
|
|
return 'Perdu';
|
|
case EquipmentStatus.outOfService:
|
|
return 'HS';
|
|
case EquipmentStatus.maintenance:
|
|
return 'En maintenance';
|
|
}
|
|
}
|
|
|
|
Color _getStatusColor(EquipmentStatus status) {
|
|
switch (status) {
|
|
case EquipmentStatus.available:
|
|
return Colors.green;
|
|
case EquipmentStatus.inUse:
|
|
return Colors.blue;
|
|
case EquipmentStatus.rented:
|
|
return Colors.orange;
|
|
case EquipmentStatus.lost:
|
|
return Colors.red;
|
|
case EquipmentStatus.outOfService:
|
|
return Colors.red;
|
|
case EquipmentStatus.maintenance:
|
|
return Colors.yellow;
|
|
}
|
|
}
|
|
}
|
|
|