Ajout de la gestion des containers (création, édition, suppression, affichage des détails).
Introduction d'un système de génération de QR codes unifié et d'un mode de sélection multiple.
**Features:**
- **Gestion des Containers :**
- Nouvelle page de gestion des containers (`container_management_page.dart`) avec recherche et filtres.
- Formulaire de création/édition de containers (`container_form_page.dart`) avec génération d'ID automatique.
- Page de détails d'un container (`container_detail_page.dart`) affichant son contenu et ses caractéristiques.
- Ajout des routes et du provider (`ContainerProvider`) nécessaires.
- **Modèle de Données :**
- Ajout du `ContainerModel` pour représenter les boîtes, flight cases, etc.
- Le modèle `EquipmentModel` a été enrichi avec des caractéristiques physiques (poids, dimensions).
- **QR Codes :**
- Nouveau service unifié (`UnifiedPDFGeneratorService`) pour générer des PDFs de QR codes pour n'importe quelle entité.
- Services `PDFGeneratorService` et `ContainerPDFGeneratorService` transformés en wrappers pour maintenir la compatibilité.
- Amélioration de la performance de la génération de QR codes en masse.
- **Interface Utilisateur (UI/UX) :**
- Nouvelle page de détails pour le matériel (`equipment_detail_page.dart`).
- Ajout d'un `SelectionModeMixin` pour gérer la sélection multiple dans les pages de gestion.
- Dialogues réutilisables pour l'affichage de QR codes (`QRCodeDialog`) et la sélection de format d'impression (`QRCodeFormatSelectorDialog`).
- Ajout d'un bouton "Gérer les boîtes" sur la page de gestion du matériel.
**Refactorisation :**
- L' `IdGenerator` a été déplacé dans le répertoire `utils` et étendu pour gérer les containers.
- Mise à jour de nombreuses dépendances `pubspec.yaml` vers des versions plus récentes.
- Séparation de la logique d'affichage des containers et du matériel dans des widgets dédiés (`ContainerHeaderCard`, `EquipmentParentContainers`, etc.).
815 lines
25 KiB
Dart
815 lines
25 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:provider/provider.dart';
|
||
import 'package:em2rp/utils/colors.dart';
|
||
import 'package:em2rp/utils/permission_gate.dart';
|
||
import 'package:em2rp/views/widgets/nav/main_drawer.dart';
|
||
import 'package:em2rp/views/widgets/nav/custom_app_bar.dart';
|
||
import 'package:em2rp/providers/container_provider.dart';
|
||
import 'package:em2rp/models/container_model.dart';
|
||
import 'package:em2rp/models/equipment_model.dart';
|
||
import 'package:em2rp/services/container_pdf_generator_service.dart';
|
||
import 'package:em2rp/views/widgets/common/qr_code_dialog.dart';
|
||
import 'package:em2rp/mixins/selection_mode_mixin.dart';
|
||
import 'package:printing/printing.dart';
|
||
import 'package:pdf/pdf.dart';
|
||
|
||
class ContainerManagementPage extends StatefulWidget {
|
||
const ContainerManagementPage({super.key});
|
||
|
||
@override
|
||
State<ContainerManagementPage> createState() =>
|
||
_ContainerManagementPageState();
|
||
}
|
||
|
||
class _ContainerManagementPageState extends State<ContainerManagementPage>
|
||
with SelectionModeMixin<ContainerManagementPage> {
|
||
final TextEditingController _searchController = TextEditingController();
|
||
ContainerType? _selectedType;
|
||
EquipmentStatus? _selectedStatus;
|
||
|
||
@override
|
||
void dispose() {
|
||
_searchController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final isMobile = MediaQuery.of(context).size.width < 800;
|
||
|
||
return PermissionGate(
|
||
requiredPermissions: const ['view_equipment'],
|
||
fallback: Scaffold(
|
||
appBar: const CustomAppBar(title: 'Accès refusé'),
|
||
drawer: const MainDrawer(currentPage: '/container_management'),
|
||
body: const Center(
|
||
child: Padding(
|
||
padding: EdgeInsets.all(24.0),
|
||
child: Text(
|
||
'Vous n\'avez pas les permissions nécessaires pour accéder à la gestion des containers.',
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(fontSize: 16),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
child: Scaffold(
|
||
appBar: isSelectionMode
|
||
? AppBar(
|
||
backgroundColor: AppColors.rouge,
|
||
leading: IconButton(
|
||
icon: const Icon(Icons.close, color: Colors.white),
|
||
onPressed: toggleSelectionMode,
|
||
),
|
||
title: Text(
|
||
'$selectedCount sélectionné(s)',
|
||
style: const TextStyle(color: Colors.white),
|
||
),
|
||
actions: [
|
||
if (hasSelection) ...[
|
||
IconButton(
|
||
icon: const Icon(Icons.qr_code, color: Colors.white),
|
||
tooltip: 'Générer QR Codes',
|
||
onPressed: _generateQRCodesForSelected,
|
||
),
|
||
IconButton(
|
||
icon: const Icon(Icons.delete, color: Colors.white),
|
||
tooltip: 'Supprimer',
|
||
onPressed: _deleteSelectedContainers,
|
||
),
|
||
],
|
||
],
|
||
)
|
||
: const CustomAppBar(title: 'Gestion des Containers'),
|
||
drawer: const MainDrawer(currentPage: '/container_management'),
|
||
floatingActionButton: !isSelectionMode
|
||
? FloatingActionButton.extended(
|
||
onPressed: () => _navigateToForm(context),
|
||
backgroundColor: AppColors.rouge,
|
||
icon: const Icon(Icons.add, color: Colors.white),
|
||
label: const Text(
|
||
'Nouveau Container',
|
||
style: TextStyle(color: Colors.white),
|
||
),
|
||
)
|
||
: null,
|
||
body: isMobile ? _buildMobileLayout() : _buildDesktopLayout(),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMobileLayout() {
|
||
return Column(
|
||
children: [
|
||
_buildSearchBar(),
|
||
_buildMobileFilters(),
|
||
Expanded(child: _buildContainerList()),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildDesktopLayout() {
|
||
return Row(
|
||
children: [
|
||
SizedBox(
|
||
width: 250,
|
||
child: _buildSidebar(),
|
||
),
|
||
const VerticalDivider(width: 1, thickness: 1),
|
||
Expanded(
|
||
child: Column(
|
||
children: [
|
||
_buildSearchBar(),
|
||
Expanded(child: _buildContainerList()),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildSearchBar() {
|
||
return Container(
|
||
padding: const EdgeInsets.all(16),
|
||
decoration: BoxDecoration(
|
||
color: Colors.white,
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: Colors.grey.withOpacity(0.1),
|
||
spreadRadius: 1,
|
||
blurRadius: 3,
|
||
offset: const Offset(0, 1),
|
||
),
|
||
],
|
||
),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: TextField(
|
||
controller: _searchController,
|
||
decoration: InputDecoration(
|
||
hintText: 'Rechercher un container...',
|
||
prefixIcon: const Icon(Icons.search, color: AppColors.rouge),
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||
),
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 16,
|
||
vertical: 12,
|
||
),
|
||
),
|
||
onChanged: (value) {
|
||
context.read<ContainerProvider>().setSearchQuery(value);
|
||
},
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
if (!isSelectionMode)
|
||
IconButton(
|
||
icon: const Icon(Icons.checklist, color: AppColors.rouge),
|
||
tooltip: 'Mode sélection',
|
||
onPressed: toggleSelectionMode,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMobileFilters() {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
color: Colors.grey.shade50,
|
||
child: SingleChildScrollView(
|
||
scrollDirection: Axis.horizontal,
|
||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||
child: Row(
|
||
children: [
|
||
_buildTypeChip(null, 'Tous'),
|
||
const SizedBox(width: 8),
|
||
...ContainerType.values.map((type) {
|
||
return Padding(
|
||
padding: const EdgeInsets.only(right: 8),
|
||
child: _buildTypeChip(type, containerTypeLabel(type)),
|
||
);
|
||
}),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTypeChip(ContainerType? type, String label) {
|
||
final isSelected = _selectedType == type;
|
||
return ChoiceChip(
|
||
label: Text(label),
|
||
selected: isSelected,
|
||
onSelected: (selected) {
|
||
setState(() {
|
||
_selectedType = selected ? type : null;
|
||
context.read<ContainerProvider>().setSelectedType(_selectedType);
|
||
});
|
||
},
|
||
selectedColor: AppColors.rouge,
|
||
labelStyle: TextStyle(
|
||
color: isSelected ? Colors.white : AppColors.noir,
|
||
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSidebar() {
|
||
return Container(
|
||
color: Colors.grey.shade50,
|
||
child: ListView(
|
||
padding: const EdgeInsets.all(16),
|
||
children: [
|
||
Text(
|
||
'Filtres',
|
||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||
fontWeight: FontWeight.bold,
|
||
color: AppColors.noir,
|
||
),
|
||
),
|
||
const SizedBox(height: 16),
|
||
|
||
// Filtre par type
|
||
Text(
|
||
'Type de container',
|
||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||
fontWeight: FontWeight.bold,
|
||
color: AppColors.noir,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
_buildFilterOption(null, 'Tous les types'),
|
||
...ContainerType.values.map((type) {
|
||
return _buildFilterOption(type, containerTypeLabel(type));
|
||
}),
|
||
|
||
const Divider(height: 32),
|
||
|
||
// Filtre par statut
|
||
Text(
|
||
'Statut',
|
||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||
fontWeight: FontWeight.bold,
|
||
color: AppColors.noir,
|
||
),
|
||
),
|
||
const SizedBox(height: 8),
|
||
_buildStatusFilter(null, 'Tous les statuts'),
|
||
_buildStatusFilter(EquipmentStatus.available, 'Disponible'),
|
||
_buildStatusFilter(EquipmentStatus.inUse, 'En prestation'),
|
||
_buildStatusFilter(EquipmentStatus.maintenance, 'En maintenance'),
|
||
_buildStatusFilter(EquipmentStatus.outOfService, 'Hors service'),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildFilterOption(ContainerType? type, String label) {
|
||
final isSelected = _selectedType == type;
|
||
return RadioListTile<ContainerType?>(
|
||
title: Text(label),
|
||
value: type,
|
||
groupValue: _selectedType,
|
||
activeColor: AppColors.rouge,
|
||
dense: true,
|
||
contentPadding: EdgeInsets.zero,
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_selectedType = value;
|
||
context.read<ContainerProvider>().setSelectedType(_selectedType);
|
||
});
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildStatusFilter(EquipmentStatus? status, String label) {
|
||
final isSelected = _selectedStatus == status;
|
||
return RadioListTile<EquipmentStatus?>(
|
||
title: Text(label),
|
||
value: status,
|
||
groupValue: _selectedStatus,
|
||
activeColor: AppColors.rouge,
|
||
dense: true,
|
||
contentPadding: EdgeInsets.zero,
|
||
onChanged: (value) {
|
||
setState(() {
|
||
_selectedStatus = value;
|
||
context.read<ContainerProvider>().setSelectedStatus(_selectedStatus);
|
||
});
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildContainerList() {
|
||
return Consumer<ContainerProvider>(
|
||
builder: (context, provider, child) {
|
||
return StreamBuilder<List<ContainerModel>>(
|
||
stream: provider.containersStream,
|
||
builder: (context, snapshot) {
|
||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||
return const Center(child: CircularProgressIndicator());
|
||
}
|
||
|
||
if (snapshot.hasError) {
|
||
return Center(
|
||
child: Text('Erreur: ${snapshot.error}'),
|
||
);
|
||
}
|
||
|
||
final containers = snapshot.data ?? [];
|
||
|
||
if (containers.isEmpty) {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Icon(
|
||
Icons.inventory_2_outlined,
|
||
size: 80,
|
||
color: Colors.grey.shade400,
|
||
),
|
||
const SizedBox(height: 16),
|
||
Text(
|
||
'Aucun container trouvé',
|
||
style: TextStyle(
|
||
fontSize: 18,
|
||
color: Colors.grey.shade600,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
return ListView.builder(
|
||
padding: const EdgeInsets.all(16),
|
||
itemCount: containers.length,
|
||
itemBuilder: (context, index) {
|
||
final container = containers[index];
|
||
return _buildContainerCard(container);
|
||
},
|
||
);
|
||
},
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildContainerCard(ContainerModel container) {
|
||
final isSelected = isItemSelected(container.id);
|
||
|
||
return Card(
|
||
margin: const EdgeInsets.only(bottom: 12),
|
||
elevation: 2,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
side: isSelected
|
||
? const BorderSide(color: AppColors.rouge, width: 2)
|
||
: BorderSide.none,
|
||
),
|
||
child: InkWell(
|
||
onTap: () {
|
||
if (isSelectionMode) {
|
||
toggleItemSelection(container.id);
|
||
} else {
|
||
_viewContainerDetails(container);
|
||
}
|
||
},
|
||
onLongPress: () {
|
||
if (!isSelectionMode) {
|
||
toggleSelectionMode();
|
||
toggleItemSelection(container.id);
|
||
}
|
||
},
|
||
borderRadius: BorderRadius.circular(8),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16),
|
||
child: Row(
|
||
children: [
|
||
if (isSelectionMode)
|
||
Padding(
|
||
padding: const EdgeInsets.only(right: 16),
|
||
child: Checkbox(
|
||
value: isSelected,
|
||
onChanged: (value) {
|
||
toggleItemSelection(container.id);
|
||
},
|
||
activeColor: AppColors.rouge,
|
||
),
|
||
),
|
||
|
||
// Icône du type de container
|
||
Icon(
|
||
_getTypeIcon(container.type),
|
||
size: 40,
|
||
color: AppColors.rouge,
|
||
),
|
||
|
||
const SizedBox(width: 16),
|
||
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
container.id,
|
||
style: const TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 16,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Text(
|
||
container.name,
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey.shade700,
|
||
),
|
||
),
|
||
const SizedBox(height: 4),
|
||
Row(
|
||
children: [
|
||
_buildInfoChip(
|
||
containerTypeLabel(container.type),
|
||
Icons.category,
|
||
),
|
||
const SizedBox(width: 8),
|
||
_buildInfoChip(
|
||
'${container.itemCount} items',
|
||
Icons.inventory,
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
|
||
const SizedBox(width: 16),
|
||
|
||
// Badge de statut
|
||
_buildStatusBadge(container.status),
|
||
|
||
if (!isSelectionMode) ...[
|
||
const SizedBox(width: 8),
|
||
PopupMenuButton<String>(
|
||
icon: const Icon(Icons.more_vert),
|
||
onSelected: (value) => _handleMenuAction(value, container),
|
||
itemBuilder: (context) => [
|
||
const PopupMenuItem(
|
||
value: 'view',
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.visibility, size: 20),
|
||
SizedBox(width: 8),
|
||
Text('Voir détails'),
|
||
],
|
||
),
|
||
),
|
||
const PopupMenuItem(
|
||
value: 'edit',
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.edit, size: 20),
|
||
SizedBox(width: 8),
|
||
Text('Modifier'),
|
||
],
|
||
),
|
||
),
|
||
const PopupMenuItem(
|
||
value: 'qr',
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.qr_code, size: 20),
|
||
SizedBox(width: 8),
|
||
Text('QR Code'),
|
||
],
|
||
),
|
||
),
|
||
const PopupMenuItem(
|
||
value: 'delete',
|
||
child: Row(
|
||
children: [
|
||
Icon(Icons.delete, color: Colors.red, size: 20),
|
||
SizedBox(width: 8),
|
||
Text('Supprimer', style: TextStyle(color: Colors.red)),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildInfoChip(String label, IconData icon) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey.shade100,
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Icon(icon, size: 14, color: Colors.grey.shade700),
|
||
const SizedBox(width: 4),
|
||
Text(
|
||
label,
|
||
style: TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.grey.shade700,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildStatusBadge(EquipmentStatus status) {
|
||
Color color;
|
||
String label;
|
||
|
||
switch (status) {
|
||
case EquipmentStatus.available:
|
||
color = Colors.green;
|
||
label = 'Disponible';
|
||
break;
|
||
case EquipmentStatus.inUse:
|
||
color = Colors.orange;
|
||
label = 'En prestation';
|
||
break;
|
||
case EquipmentStatus.maintenance:
|
||
color = Colors.blue;
|
||
label = 'Maintenance';
|
||
break;
|
||
case EquipmentStatus.outOfService:
|
||
color = Colors.red;
|
||
label = 'Hors service';
|
||
break;
|
||
default:
|
||
color = Colors.grey;
|
||
label = 'Autre';
|
||
}
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
color: color.withOpacity(0.1),
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(color: color),
|
||
),
|
||
child: Text(
|
||
label,
|
||
style: TextStyle(
|
||
color: color,
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
IconData _getTypeIcon(ContainerType type) {
|
||
switch (type) {
|
||
case ContainerType.flightCase:
|
||
return Icons.work;
|
||
case ContainerType.pelicase:
|
||
return Icons.work_outline;
|
||
case ContainerType.bag:
|
||
return Icons.shopping_bag;
|
||
case ContainerType.openCrate:
|
||
return Icons.inventory_2;
|
||
case ContainerType.toolbox:
|
||
return Icons.handyman;
|
||
}
|
||
}
|
||
|
||
|
||
void _handleMenuAction(String action, ContainerModel container) {
|
||
switch (action) {
|
||
case 'view':
|
||
_viewContainerDetails(container);
|
||
break;
|
||
case 'edit':
|
||
_editContainer(container);
|
||
break;
|
||
case 'qr':
|
||
showDialog(
|
||
context: context,
|
||
builder: (context) => QRCodeDialog.forContainer(container),
|
||
);
|
||
break;
|
||
case 'delete':
|
||
_deleteContainer(container);
|
||
break;
|
||
}
|
||
}
|
||
|
||
void _navigateToForm(BuildContext context) async {
|
||
final result = await Navigator.pushNamed(context, '/container_form');
|
||
if (result == true) {
|
||
// Rafraîchir la liste
|
||
}
|
||
}
|
||
|
||
void _viewContainerDetails(ContainerModel container) async {
|
||
await Navigator.pushNamed(
|
||
context,
|
||
'/container_detail',
|
||
arguments: container,
|
||
);
|
||
}
|
||
|
||
void _editContainer(ContainerModel container) async {
|
||
await Navigator.pushNamed(
|
||
context,
|
||
'/container_form',
|
||
arguments: container,
|
||
);
|
||
}
|
||
|
||
|
||
Future<void> _generateQRCodesForSelected() async {
|
||
if (!hasSelection) return;
|
||
|
||
// Récupérer les containers sélectionnés
|
||
final containerProvider = context.read<ContainerProvider>();
|
||
final List<ContainerModel> selectedContainers = [];
|
||
final Map<String, List<EquipmentModel>> containerEquipmentMap = {};
|
||
|
||
for (final id in selectedIds) {
|
||
final container = await containerProvider.getContainerById(id);
|
||
if (container != null) {
|
||
selectedContainers.add(container);
|
||
// Charger les équipements pour ce container
|
||
final equipment = await containerProvider.getContainerEquipment(id);
|
||
containerEquipmentMap[id] = equipment;
|
||
}
|
||
}
|
||
|
||
if (selectedContainers.isEmpty) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('Aucun container trouvé')),
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Afficher le dialogue de sélection de format
|
||
final format = await showDialog<ContainerQRLabelFormat>(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: const Text('Format des étiquettes'),
|
||
content: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
ListTile(
|
||
leading: const Icon(Icons.qr_code_2),
|
||
title: const Text('Petits QR codes'),
|
||
subtitle: const Text('2×2 cm - QR code + ID (20 par page)'),
|
||
onTap: () => Navigator.pop(context, ContainerQRLabelFormat.small),
|
||
),
|
||
ListTile(
|
||
leading: const Icon(Icons.qr_code),
|
||
title: const Text('QR codes moyens'),
|
||
subtitle: const Text('4×4 cm - QR code + ID (6 par page)'),
|
||
onTap: () => Navigator.pop(context, ContainerQRLabelFormat.medium),
|
||
),
|
||
ListTile(
|
||
leading: const Icon(Icons.label),
|
||
title: const Text('Grandes étiquettes'),
|
||
subtitle: const Text('QR code + ID + Type + Contenu (6 par page)'),
|
||
onTap: () => Navigator.pop(context, ContainerQRLabelFormat.large),
|
||
),
|
||
],
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context),
|
||
child: const Text('Annuler'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (format == null || !mounted) return;
|
||
|
||
// Générer et afficher le PDF
|
||
try {
|
||
final pdfBytes = await ContainerPDFGeneratorService.generateQRCodesPDF(
|
||
containerList: selectedContainers,
|
||
containerEquipmentMap: containerEquipmentMap,
|
||
format: format,
|
||
);
|
||
|
||
if (mounted) {
|
||
await Printing.layoutPdf(
|
||
onLayout: (PdfPageFormat format) async => pdfBytes,
|
||
);
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('Erreur lors de la génération: $e')),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> _deleteContainer(ContainerModel container) async {
|
||
final confirm = await showDialog<bool>(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: const Text('Confirmer la suppression'),
|
||
content: Text(
|
||
'Êtes-vous sûr de vouloir supprimer le container "${container.name}" ?\n\n'
|
||
'Cette action est irréversible.',
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context, false),
|
||
child: const Text('Annuler'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () => Navigator.pop(context, true),
|
||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||
child: const Text('Supprimer'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (confirm == true && mounted) {
|
||
try {
|
||
await context.read<ContainerProvider>().deleteContainer(container.id);
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('Container supprimé avec succès')),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('Erreur lors de la suppression: $e')),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Future<void> _deleteSelectedContainers() async {
|
||
final confirm = await showDialog<bool>(
|
||
context: context,
|
||
builder: (context) => AlertDialog(
|
||
title: const Text('Confirmer la suppression'),
|
||
content: Text(
|
||
'Êtes-vous sûr de vouloir supprimer $selectedCount container(s) ?\n\n'
|
||
'Cette action est irréversible.',
|
||
),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () => Navigator.pop(context, false),
|
||
child: const Text('Annuler'),
|
||
),
|
||
ElevatedButton(
|
||
onPressed: () => Navigator.pop(context, true),
|
||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||
child: const Text('Supprimer'),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
|
||
if (confirm == true && mounted) {
|
||
try {
|
||
final provider = context.read<ContainerProvider>();
|
||
for (final id in selectedIds) {
|
||
await provider.deleteContainer(id);
|
||
}
|
||
disableSelectionMode();
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('Containers supprimés avec succès')),
|
||
);
|
||
}
|
||
} catch (e) {
|
||
if (mounted) {
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(content: Text('Erreur lors de la suppression: $e')),
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|