You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

439 lines
13 KiB

// pages/facture_screen.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/command_detail.dart';
import '../services/pdf_service.dart';
class FactureScreen extends StatefulWidget {
final CommandeDetail commande;
final String paymentMethod;
const FactureScreen({
Key? key,
required this.commande,
required this.paymentMethod,
}) : super(key: key);
@override
_FactureScreenState createState() => _FactureScreenState();
}
class _FactureScreenState extends State<FactureScreen> {
String get paymentMethodText {
switch (widget.paymentMethod) {
case 'mvola':
return 'MVola';
case 'carte':
return 'CB';
case 'especes':
return 'Espèces';
default:
return 'CB';
}
}
String get factureNumber {
return 'F${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.of(context).pop(),
),
title: const Text(
'Retour',
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
actions: [
Container(
margin: const EdgeInsets.only(right: 16, top: 8, bottom: 8),
child: ElevatedButton.icon(
onPressed: _printReceipt,
icon: const Icon(Icons.print, size: 18),
label: const Text('Imprimer'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF28A745),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
),
),
],
),
body: Center(
child: Container(
width: 400,
margin: const EdgeInsets.all(20),
child: Card(
elevation: 2,
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: const EdgeInsets.all(40),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeader(),
const SizedBox(height: 30),
_buildFactureInfo(),
const SizedBox(height: 30),
_buildItemsList(),
const SizedBox(height: 20),
_buildTotal(),
const SizedBox(height: 30),
_buildFooter(),
],
),
),
),
),
),
);
}
Widget _buildHeader() {
return Column(
children: [
const Text(
'RESTAURANT',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const SizedBox(height: 12),
const Text(
'Adresse: 123 Rue de la Paix',
style: TextStyle(fontSize: 12, color: Colors.black87),
),
const Text(
'Contact: +33 1 23 45 67 89',
style: TextStyle(fontSize: 12, color: Colors.black87),
),
],
);
}
Widget _buildFactureInfo() {
final now = DateTime.now();
final dateStr =
'${now.day.toString().padLeft(2, '0')}/${now.month.toString().padLeft(2, '0')}/${now.year}';
final timeStr =
'${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
return Column(
children: [
Text(
'Facture n° $factureNumber',
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
const SizedBox(height: 4),
Text(
'Date: $dateStr $timeStr',
style: const TextStyle(fontSize: 12, color: Colors.black87),
),
const SizedBox(height: 4),
Text(
'Table: ${widget.commande.tableId}',
style: const TextStyle(fontSize: 12, color: Colors.black87),
),
const SizedBox(height: 4),
Text(
'Paiement: $paymentMethodText',
style: const TextStyle(fontSize: 12, color: Colors.black87),
),
],
);
}
Widget _buildItemsList() {
return Column(
children: [
const Padding(
padding: EdgeInsets.only(bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Qté Désignation',
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
),
Text(
'Prix',
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
),
],
),
),
const Divider(height: 1, color: Colors.black26),
const SizedBox(height: 10),
...widget.commande.items
.map(
(item) => Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'${item.quantite} ${item.menuNom}',
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
),
),
),
Text(
'${(item.prixUnitaire * item.quantite).toStringAsFixed(2)} MGA',
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
),
),
],
),
),
)
.toList(),
],
);
}
Widget _buildTotal() {
return Column(
children: [
const Divider(height: 1, color: Colors.black26),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Total:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Text(
'${widget.commande.totalTtc.toStringAsFixed(2)}',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
],
);
}
Widget _buildFooter() {
return const Text(
'Merci et à bientôt !',
style: TextStyle(
fontSize: 12,
fontStyle: FontStyle.italic,
color: Colors.black54,
),
);
}
void _printReceipt() async {
bool isPrinting;
setState(() => isPrinting = true);
try {
// Vérifier si l'impression est disponible
final canPrint = await PlatformPrintService.canPrint();
if (!canPrint) {
// Si pas d'imprimante, proposer seulement la sauvegarde
_showSaveOnlyDialog();
return;
}
// Afficher les options d'impression
final action = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Row(
children: [
Icon(Icons.print, color: Theme.of(context).primaryColor),
const SizedBox(width: 8),
const Text('Options d\'impression'),
],
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Plateforme: ${_getPlatformName()}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 16),
const Text('Que souhaitez-vous faire ?'),
],
),
actions: [
TextButton.icon(
onPressed: () => Navigator.of(context).pop('print'),
icon: const Icon(Icons.print),
label: const Text('Imprimer'),
),
TextButton.icon(
onPressed: () => Navigator.of(context).pop('save'),
icon: const Icon(Icons.save),
label: const Text('Sauvegarder PDF'),
),
TextButton(
onPressed: () => Navigator.of(context).pop('cancel'),
child: const Text('Annuler'),
),
],
);
},
);
if (action == null || action == 'cancel') return;
HapticFeedback.lightImpact();
bool success = false;
if (action == 'print') {
success = await PlatformPrintService.printFacture(
commande: widget.commande,
paymentMethod: widget.paymentMethod,
);
} else if (action == 'save') {
success = await PlatformPrintService.saveFacturePdf(
commande: widget.commande,
paymentMethod: widget.paymentMethod,
);
}
if (success) {
_showSuccessMessage(
action == 'print'
? 'Facture envoyée à l\'imprimante ${_getPlatformName()}'
: 'PDF sauvegardé et partagé',
);
} else {
_showErrorMessage(
'Erreur lors de ${action == 'print' ? 'l\'impression' : 'la sauvegarde'}',
);
}
} catch (e) {
_showErrorMessage('Erreur: $e');
} finally {
setState(() => isPrinting = false);
}
}
void _showSaveOnlyDialog() async {
final shouldSave = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Aucune imprimante'),
content: const Text(
'Aucune imprimante détectée. Voulez-vous sauvegarder le PDF ?',
),
actions: [
TextButton.icon(
onPressed: () => Navigator.of(context).pop(true),
icon: const Icon(Icons.save),
label: const Text('Sauvegarder'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Annuler'),
),
],
);
},
);
if (shouldSave == true) {
final success = await PlatformPrintService.saveFacturePdf(
commande: widget.commande,
paymentMethod: widget.paymentMethod,
);
if (success) {
_showSuccessMessage('PDF sauvegardé avec succès');
} else {
_showErrorMessage('Erreur lors de la sauvegarde');
}
}
}
void _showSuccessMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.check_circle, color: Colors.white),
const SizedBox(width: 8),
Expanded(child: Text(message)),
],
),
backgroundColor: const Color(0xFF28A745),
duration: const Duration(seconds: 3),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
margin: const EdgeInsets.all(16),
behavior: SnackBarBehavior.floating,
),
);
Future.delayed(const Duration(seconds: 3), () {
if (mounted) {
Navigator.of(context).popUntil((route) => route.isFirst);
}
});
}
void _showErrorMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.error, color: Colors.white),
const SizedBox(width: 8),
Expanded(child: Text(message)),
],
),
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
margin: const EdgeInsets.all(16),
behavior: SnackBarBehavior.floating,
),
);
}
String _getPlatformName() {
if (Platform.isAndroid) return 'Android';
if (Platform.isMacOS) return 'macOS';
if (Platform.isWindows) return 'Windows';
return 'cette plateforme';
}
}