Browse Source

push

master
andrymodeste 4 months ago
parent
commit
b455feca42
  1. 5
      lib/layouts/main_layout.dart
  2. 6
      lib/main.dart
  3. 6
      lib/pages/caisse_screen.dart
  4. 4
      lib/pages/cart_page.dart
  5. 67
      lib/pages/commandes_screen.dart
  6. 65
      lib/pages/facture_screen.dart
  7. 73
      lib/pages/historique_commande.dart
  8. 467
      lib/pages/information.dart
  9. 77
      lib/services/pdf_service.dart
  10. 22
      lib/services/restaurant_api_service.dart
  11. 45
      lib/widgets/bottom_navigation.dart
  12. 8
      pubspec.lock

5
lib/layouts/main_layout.dart

@ -38,6 +38,8 @@ class _MainLayoutState extends State<MainLayout> {
return 5;
case '/historique':
return 6;
case '/information':
return 7;
default:
return 0;
}
@ -71,6 +73,9 @@ class _MainLayoutState extends State<MainLayout> {
case 6:
route = '/historique';
break;
case 7:
route = '/information';
break;
default:
route = '/tables';
}

6
lib/main.dart

@ -6,6 +6,7 @@ import 'pages/commandes_screen.dart';
import 'pages/login_screen.dart';
import 'pages/menus_screen.dart';
import 'pages/historique_commande.dart';
import 'pages/information.dart';
import 'pages/encaissement_screen.dart'; // NOUVEAU
void main() {
@ -58,6 +59,11 @@ class MyApp extends StatelessWidget {
currentRoute: '/historique',
child: OrderHistoryPage(),
),
'/information':
(context) => MainLayout(
currentRoute: '/information',
child: PrintTemplateManagementScreen(),
),
},
);
}

6
lib/pages/caisse_screen.dart

@ -6,6 +6,8 @@ import '../models/payment_method.dart';
import '../services/restaurant_api_service.dart';
import 'package:intl/intl.dart';
import 'information.dart';
class CaisseScreen extends StatefulWidget {
final String commandeId;
final int tableNumber;
@ -22,6 +24,7 @@ class CaisseScreen extends StatefulWidget {
class _CaisseScreenState extends State<CaisseScreen> {
CommandeDetail? commande;
PrintTemplate? template;
PaymentMethod? selectedPaymentMethod;
bool isLoading = true;
bool isProcessingPayment = false;
@ -70,8 +73,10 @@ class _CaisseScreenState extends State<CaisseScreen> {
final result = await RestaurantApiService.getCommandeDetails(
widget.commandeId,
);
final loadedTemplate = await RestaurantApiService.getPrintTemplate();
setState(() {
commande = result;
template = loadedTemplate;
isLoading = false;
});
} catch (e) {
@ -111,6 +116,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
MaterialPageRoute(
builder: (context) => FactureScreen(
commande: commande!,
template: template!,
paymentMethod: selectedPaymentMethod!.id,
),
),

4
lib/pages/cart_page.dart

@ -13,6 +13,8 @@ import 'package:itrimobe/services/pdf_service.dart';
import '../layouts/main_layout.dart';
import 'package:intl/intl.dart';
import 'information.dart';
class CartPage extends StatefulWidget {
final int tableId;
final int personne;
@ -462,12 +464,14 @@ class _CartPageState extends State<CartPage> {
// Convertir le Map en objet CommandeDetail
var commandeDetail = CommandeDetail.fromJson(commandeData['data']);
var template = PrintTemplate.fromJson(commandeData['data']);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder:
(context) => FactureScreen(
commande: commandeDetail,
template: template,
paymentMethod: selectedPaymentMethod!.id,
tablename:
widget.tablename ??

67
lib/pages/commandes_screen.dart

@ -249,43 +249,50 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
print('Error creating order: $e');
}
}
Future<void> deleteOrder(Order order) async {
try {
final response = await http.delete(
Uri.parse('$baseUrl/commandes/${order.id}'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
Future<void> deleteOrder(Order order) async {
try {
final response = await http.delete(
Uri.parse('$baseUrl/commandes/${order.id}'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
);
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
if (response.statusCode == 200) {
final responseData = json.decode(response.body);
if (responseData['success'] == true) {
setState(() {
orders.remove(order);
});
if (responseData['success'] == true) {
setState(() {
orders.remove(order);
});
// Mettre la table associée en "available"
await updateTableStatus(order.tableId, "available");
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Commande supprimée avec succès'),
backgroundColor: Colors.green,
),
);
}
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Commande supprimée avec succès'),
backgroundColor: Colors.green,
),
);
} else {
throw Exception('Suppression échouée du côté serveur.');
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la suppression: $e'),
backgroundColor: Colors.red,
),
);
print('Error deleting order: $e');
} else {
throw Exception('Échec suppression: ${response.statusCode}');
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur lors de la suppression: $e'),
backgroundColor: Colors.red,
),
);
print('Error deleting order: $e');
}
}
List<Order> get activeOrders {
return orders

65
lib/pages/facture_screen.dart

@ -6,8 +6,10 @@ import 'package:flutter/services.dart';
import '../models/command_detail.dart';
import '../services/pdf_service.dart';
import 'package:intl/intl.dart';
import 'information.dart';
class FactureScreen extends StatefulWidget {
final PrintTemplate template;
final CommandeDetail commande;
final String paymentMethod;
final String? tablename;
@ -17,6 +19,7 @@ class FactureScreen extends StatefulWidget {
required this.commande,
required this.paymentMethod,
this.tablename,
required this.template,
});
@override
@ -38,9 +41,14 @@ class _FactureScreenState extends State<FactureScreen> {
}
String get factureNumber {
return 'F${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
return 'F${DateTime.now().millisecondsSinceEpoch.toString().substring(4)}';
}
String formatTemplateContent(String content) {
return content.replaceAll('\\r\\n', '\n').replaceAll('\\n', '\n');
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -116,37 +124,29 @@ class _FactureScreenState extends State<FactureScreen> {
);
}
Widget _buildHeader() {
return const Column(
children: [
Text(
'RESTAURANT ITRIMOBE',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
SizedBox(height: 12),
Text(
'Adresse: Moramanga, Madagascar',
style: TextStyle(fontSize: 12, color: Colors.black87),
),
Text(
'Contact: +261 34 12 34 56',
style: TextStyle(fontSize: 12, color: Colors.black87),
Widget _buildHeader() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
widget.template.title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
Text(
'NIF: 4002141594',
style: TextStyle(fontSize: 12, color: Colors.black87),
),
Text(
'STAT: 10715 33 2025 0 00414',
style: TextStyle(fontSize: 12, color: Colors.black87),
),
],
);
}
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Text(
formatTemplateContent(widget.template.content),
style: const TextStyle(fontSize: 12, color: Colors.black87),
textAlign: TextAlign.center,
),
],
);
}
Widget _buildFactureInfo() {
final now = DateTime.now();
@ -333,11 +333,13 @@ class _FactureScreenState extends State<FactureScreen> {
if (action == 'print') {
success = await PlatformPrintService.printFacture(
commande: widget.commande,
template: widget.template,
paymentMethod: widget.paymentMethod,
);
} else if (action == 'save') {
success = await PlatformPrintService.saveFacturePdf(
commande: widget.commande,
template: widget.template,
paymentMethod: widget.paymentMethod,
);
}
@ -387,6 +389,7 @@ class _FactureScreenState extends State<FactureScreen> {
if (shouldSave == true) {
final success = await PlatformPrintService.saveFacturePdf(
commande: widget.commande,
template: widget.template,
paymentMethod: widget.paymentMethod,
);

73
lib/pages/historique_commande.dart

@ -54,34 +54,19 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
final response = await http.get(uri, headers: _headers);
print('=== DÉBUT DEBUG RESPONSE ===');
print('Status Code: ${response.statusCode}');
print('Response Body: ${response.body}');
if (response.statusCode == 200) {
final dynamic responseBody = json.decode(response.body);
print('=== PARSED RESPONSE ===');
print('Type: ${responseBody.runtimeType}');
print('Content: $responseBody');
List<dynamic> data = [];
// Gestion améliorée de la réponse
if (responseBody is Map<String, dynamic>) {
print('=== RESPONSE EST UN MAP ===');
print('Keys disponibles: ${responseBody.keys.toList()}');
// Structure: {"success": true, "data": {"commandes": [...], "pagination": {...}}}
if (responseBody.containsKey('data') && responseBody['data'] is Map<String, dynamic>) {
final dataMap = responseBody['data'] as Map<String, dynamic>;
print('=== DATA MAP TROUVÉ ===');
print('Data keys: ${dataMap.keys.toList()}');
if (dataMap.containsKey('commandes')) {
final commandesValue = dataMap['commandes'];
print('=== COMMANDES TROUVÉES ===');
print('Type commandes: ${commandesValue.runtimeType}');
print('Nombre de commandes: ${commandesValue is List ? commandesValue.length : 'pas une liste'}');
if (commandesValue is List<dynamic>) {
data = commandesValue;
@ -96,8 +81,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
currentPage = pagination['currentPage'] ?? page;
totalPages = pagination['totalPages'] ?? 1;
totalItems = pagination['totalItems'] ?? data.length;
print('=== PAGINATION ===');
print('Page: $currentPage/$totalPages, Total: $totalItems');
}
} else {
// Si pas de pagination dans la réponse, calculer approximativement
@ -106,7 +89,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
totalPages = (totalItems / itemsPerPage).ceil();
}
} else {
print('=== PAS DE COMMANDES DANS DATA ===');
totalItems = 0;
currentPage = 1;
totalPages = 1;
@ -114,7 +96,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
} else if (responseBody.containsKey('commandes')) {
// Fallback: commandes directement dans responseBody
final commandesValue = responseBody['commandes'];
print('=== COMMANDES DIRECTES ===');
if (commandesValue is List<dynamic>) {
data = commandesValue;
@ -125,14 +106,11 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
currentPage = page;
totalPages = (totalItems / itemsPerPage).ceil();
} else {
print('=== STRUCTURE INCONNUE ===');
print('Clés disponibles: ${responseBody.keys.toList()}');
totalItems = 0;
currentPage = 1;
totalPages = 1;
}
} else if (responseBody is List<dynamic>) {
print('=== RESPONSE EST UNE LISTE ===');
data = responseBody;
totalItems = data.length;
currentPage = page;
@ -141,47 +119,22 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}');
}
print('=== DONNÉES EXTRAITES ===');
print('Nombre d\'éléments: ${data.length}');
print('Data: $data');
// Conversion sécurisée avec prints détaillés
List<CommandeData> parsedCommandes = [];
for (int i = 0; i < data.length; i++) {
try {
final item = data[i];
print('=== ITEM $i ===');
print('Type: ${item.runtimeType}');
print('Contenu complet: $item');
if (item is Map<String, dynamic>) {
print('--- ANALYSE DES CHAMPS ---');
item.forEach((key, value) {
print('$key: $value (${value.runtimeType})');
});
final commandeData = CommandeData.fromJson(item);
print('--- COMMANDE PARSÉE ---');
print('ID: ${commandeData.id}');
print('Numéro: ${commandeData.numeroCommande}');
print('Table name: ${commandeData.tablename}');
print('Serveur: ${commandeData.serveur}');
print('Date commande: ${commandeData.dateCommande}');
print('Date paiement: ${commandeData.datePaiement}');
print('Total TTC: ${commandeData.totalTtc}');
print('Mode paiement: ${commandeData.modePaiement}');
print('Nombre d\'items: ${commandeData.items?.length ?? 0}');
if (commandeData.items != null) {
print('--- ITEMS DE LA COMMANDE ---');
for (int j = 0; j < commandeData.items!.length; j++) {
final commandeItem = commandeData.items![j];
print('Item $j:');
print(' - Menu nom: ${commandeItem.menuNom}');
print(' - Quantité: ${commandeItem.quantite}');
print(' - Prix unitaire: ${commandeItem.prixUnitaire}');
print(' - Total: ${commandeItem.totalItem}');
print(' - Commentaires: ${commandeItem.commentaires}');
}
}
@ -196,9 +149,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
}
}
print('=== RÉSULTAT FINAL ===');
print('Nombre de commandes parsées: ${parsedCommandes.length}');
setState(() {
commandes = parsedCommandes;
isLoading = false;
@ -1081,8 +1031,6 @@ class CommandeData {
items: items,
tablename: tablename,
);
print('=== COMMANDE PARSÉE AVEC SUCCÈS ===');
return result;
} catch (e, stackTrace) {
print('=== ERREUR PARSING COMMANDE ===');
@ -1109,7 +1057,6 @@ class CommandeData {
if (value is String) {
try {
final result = DateTime.parse(value);
print('String to datetime: "$value" -> $result');
return result;
} catch (e) {
print('Erreur parsing date: $value - $e');
@ -1121,8 +1068,6 @@ class CommandeData {
}
static List<CommandeItem>? _parseItems(dynamic value) {
print('=== PARSING ITEMS ===');
print('Items bruts: $value (${value.runtimeType})');
if (value == null) {
print('Items null');
@ -1137,20 +1082,14 @@ class CommandeData {
try {
List<CommandeItem> result = [];
for (int i = 0; i < value.length; i++) {
print('--- ITEM $i ---');
final item = value[i];
print('Item brut: $item (${item.runtimeType})');
if (item is Map<String, dynamic>) {
final commandeItem = CommandeItem.fromJson(item);
result.add(commandeItem);
print('Item parsé: ${commandeItem.menuNom}');
} else {
print('Item $i n\'est pas un Map');
}
}
print('Total items parsés: ${result.length}');
return result;
} catch (e) {
print('Erreur parsing items: $e');
@ -1208,32 +1147,23 @@ class CommandeItem {
factory CommandeItem.fromJson(Map<String, dynamic> json) {
try {
print('=== PARSING COMMANDE ITEM ===');
print('JSON item: $json');
// Debug chaque champ
final id = json['id'] ?? 0;
print('ID: ${json['id']} -> $id');
final commandeId = json['commande_id'] ?? 0;
print('Commande ID: ${json['commande_id']} -> $commandeId');
final menuId = json['menu_id'] ?? 0;
print('Menu ID: ${json['menu_id']} -> $menuId');
final quantite = json['quantite'] ?? json['quantity'] ?? 0;
print('Quantité: ${json['quantite']} / ${json['quantity']} -> $quantite');
final prixUnitaire = CommandeData._parseDouble(json['prix_unitaire']) ??
CommandeData._parseDouble(json['unit_price']) ?? 0.0;
print('Prix unitaire: ${json['prix_unitaire']} / ${json['unit_price']} -> $prixUnitaire');
final totalItem = CommandeData._parseDouble(json['total_item']) ??
CommandeData._parseDouble(json['total']) ?? 0.0;
print('Total item: ${json['total_item']} / ${json['total']} -> $totalItem');
final commentaires = json['commentaires']?.toString() ?? json['comments']?.toString();
print('Commentaires: ${json['commentaires']} / ${json['comments']} -> $commentaires');
final statut = json['statut']?.toString() ?? json['status']?.toString() ?? '';
final menuNom = json['menu_nom']?.toString() ??
@ -1242,15 +1172,12 @@ class CommandeItem {
final menuDescription = json['menu_description']?.toString() ??
json['description']?.toString() ?? '';
print('Menu description: ${json['menu_description']} / ${json['description']} -> $menuDescription');
final menuPrixActuel = CommandeData._parseDouble(json['menu_prix_actuel']) ??
CommandeData._parseDouble(json['current_price']) ?? 0.0;
print('Menu prix actuel: ${json['menu_prix_actuel']} / ${json['current_price']} -> $menuPrixActuel');
final tablename = json['tablename']?.toString() ??
json['table_name']?.toString() ?? '';
print('Table name: ${json['tablename']} / ${json['table_name']} -> $tablename');
final result = CommandeItem(
id: id,

467
lib/pages/information.dart

@ -0,0 +1,467 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Gestion des Templates d\'Impression',
theme: ThemeData(
primarySwatch: Colors.green,
fontFamily: 'Roboto',
),
home: PrintTemplateManagementScreen(),
);
}
}
class PrintTemplate {
final int id;
final String title;
final String content;
final String createdAt;
final String updatedAt;
PrintTemplate({
required this.id,
required this.title,
required this.content,
required this.createdAt,
required this.updatedAt,
});
factory PrintTemplate.fromJson(Map<String, dynamic> json) {
return PrintTemplate(
id: json['id'],
title: json['title'],
content: json['content'],
createdAt: json['created_at'],
updatedAt: json['updated_at'],
);
}
}
class ApiResponse {
final bool success;
final List<PrintTemplate> data;
final Map<String, dynamic> pagination;
ApiResponse({
required this.success,
required this.data,
required this.pagination,
});
factory ApiResponse.fromJson(Map<String, dynamic> json) {
var dataList = json['data'] as List;
List<PrintTemplate> templates = dataList.map((item) => PrintTemplate.fromJson(item)).toList();
return ApiResponse(
success: json['success'],
data: templates,
pagination: json['pagination'],
);
}
}
class PrintTemplateManagementScreen extends StatefulWidget {
@override
_PrintTemplateManagementScreenState createState() => _PrintTemplateManagementScreenState();
}
class _PrintTemplateManagementScreenState extends State<PrintTemplateManagementScreen> {
List<PrintTemplate> templates = [];
bool isLoading = true;
String? errorMessage;
// Remplacez par votre URL d'API réelle
final String apiBaseUrl = 'https://restaurant.careeracademy.mg';
@override
void initState() {
super.initState();
fetchTemplates();
}
Future<void> fetchTemplates() async {
setState(() {
isLoading = true;
errorMessage = null;
});
try {
final response = await http.get(
Uri.parse('$apiBaseUrl/api/print-templates'),
headers: {
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
final apiResponse = ApiResponse.fromJson(json.decode(response.body));
setState(() {
templates = apiResponse.data;
isLoading = false;
});
} else {
setState(() {
errorMessage = 'Erreur HTTP: ${response.statusCode}';
isLoading = false;
});
}
} catch (e) {
print('Error fetching templates: $e');
setState(() {
errorMessage = 'Erreur de connexion: $e';
isLoading = false;
});
}
}
Future<void> updateTemplate(int id, String title, String content) async {
try {
final response = await http.put(
Uri.parse('$apiBaseUrl/api/print-templates/$id'),
headers: {
'Content-Type': 'application/json',
},
body: json.encode({
'title': title,
'content': content,
}),
);
if (response.statusCode == 200) {
// Recharger les templates après modification
await fetchTemplates();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Template modifié avec succès')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur lors de la modification: ${response.statusCode}')),
);
}
} catch (e) {
print('Error updating template: $e');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur de connexion: $e')),
);
}
}
// Fonction pour formater le contenu en remplaçant \r\n par des sauts de ligne
String _formatContent(String content) {
return content.replaceAll('\\r\\n', '\n').replaceAll('\\n', '\n');
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[100],
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Gestion des Templates d\'Impression',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
SizedBox(height: 5),
Text(
'Gérez les templates d\'impression de votre système',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
],
),
IconButton(
onPressed: fetchTemplates,
icon: Icon(Icons.refresh),
tooltip: 'Actualiser',
),
],
),
SizedBox(height: 30),
// Content
Expanded(
child: isLoading
? _buildLoadingWidget()
: errorMessage != null
? _buildErrorWidget()
: _buildTemplateTable(),
),
],
),
),
);
}
Widget _buildLoadingWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Chargement des templates...'),
],
),
);
}
Widget _buildErrorWidget() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red),
SizedBox(height: 16),
Text(
'Erreur de chargement',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(errorMessage ?? 'Erreur inconnue'),
SizedBox(height: 16),
ElevatedButton(
onPressed: fetchTemplates,
child: Text('Réessayer'),
),
],
),
);
}
Widget _buildTemplateTable() {
if (templates.isEmpty) {
return Center(
child: Text(
'Aucun template trouvé',
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
),
);
}
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
children: [
// Table Header
Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
child: Row(
children: [
Expanded(flex: 3, child: _buildHeaderCell('Titre')),
Expanded(flex: 7, child: _buildHeaderCell('Contenu')),
Expanded(flex: 1, child: _buildHeaderCell('Actions')),
],
),
),
// Table Body
Expanded(
child: ListView.builder(
itemCount: templates.length,
itemBuilder: (context, index) {
return _buildTemplateRow(templates[index]);
},
),
),
],
),
);
}
Widget _buildHeaderCell(String text) {
return Text(
text,
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.grey[700],
fontSize: 14,
),
);
}
Widget _buildTemplateRow(PrintTemplate template) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), // Augmenté le padding vertical
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey[200]!, width: 1),
),
),
child: IntrinsicHeight( // Pour que toutes les cellules aient la même hauteur
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Text(
template.title,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
Expanded(
flex: 7,
child: Container(
constraints: BoxConstraints(minHeight: 80), // Hauteur minimum pour le contenu
child: Text(
_formatContent(template.content),
style: TextStyle(
color: Colors.grey[700],
fontSize: 14,
height: 1.4, // Espacement entre les lignes
),
),
),
),
Expanded(
flex: 1,
child: Align(
alignment: Alignment.topCenter,
child: IconButton(
onPressed: () {
_showEditTemplateDialog(context, template);
},
icon: Icon(Icons.edit, color: Colors.blue, size: 20),
tooltip: 'Modifier',
),
),
),
],
),
),
);
}
void _showEditTemplateDialog(BuildContext context, PrintTemplate template) {
final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController(text: template.title);
final _contentController = TextEditingController(text: _formatContent(template.content));
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Modifier Template'),
content: Container(
width: double.maxFinite,
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildTextField(_titleController, 'Titre', true),
SizedBox(height: 16),
TextFormField(
controller: _contentController,
maxLines: 10, // Augmenté le nombre de lignes
decoration: InputDecoration(
labelText: 'Contenu',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
alignLabelWithHint: true,
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Ce champ est requis';
}
return null;
},
),
],
),
),
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Annuler'),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Reconvertir les sauts de ligne pour l'API
String contentForApi = _contentController.text.replaceAll('\n', '\\r\\n');
updateTemplate(
template.id,
_titleController.text,
contentForApi,
);
Navigator.of(context).pop();
}
},
child: Text('Modifier'),
),
],
);
},
);
}
Widget _buildTextField(TextEditingController controller, String label, bool required) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: label,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
validator: required ? (value) {
if (value == null || value.isEmpty) {
return 'Ce champ est requis';
}
return null;
} : null,
),
);
}
}

77
lib/services/pdf_service.dart

@ -13,6 +13,8 @@ import 'package:permission_handler/permission_handler.dart';
import '../models/command_detail.dart';
import 'package:intl/intl.dart';
import '../pages/information.dart';
class PlatformPrintService {
// Format spécifique 58mm pour petites imprimantes - CENTRÉ POUR L'IMPRESSION
static const PdfPageFormat ticket58mmFormat = PdfPageFormat(
@ -44,6 +46,7 @@ class PlatformPrintService {
// Générer PDF optimisé pour 58mm - VERSION IDENTIQUE À L'ÉCRAN
static Future<Uint8List> _generate58mmTicketPdf({
required CommandeDetail commande,
required PrintTemplate template,
required String paymentMethod,
}) async {
final pdf = pw.Document();
@ -51,16 +54,12 @@ static Future<Uint8List> _generate58mmTicketPdf({
const double titleSize = 8;
const double headerSize = 8;
const double bodySize = 7;
const double smallSize = 6;
const double smallSize = 5;
const double lineHeight = 1.2;
final restaurantInfo = {
'nom': 'RESTAURANT ITRIMOBE',
'adresse': 'Moramanga, Madagascar',
'contact': '+261 34 12 34 56',
'nif': '4002141594',
'stat': '10715 33 2025 0 00414',
};
final restauranTitle = template.title ?? 'Nom du restaurant';
final restaurantContent = template.content ?? 'Adresse inconnue';
final factureNumber = 'F${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
final dateTime = DateTime.now();
@ -79,6 +78,9 @@ static Future<Uint8List> _generate58mmTicketPdf({
default:
paymentMethodText = 'CB';
}
String formatTemplateContent(String content) {
return content.replaceAll('\\r\\n', '\n').replaceAll('\\n', '\n');
}
pdf.addPage(
pw.Page(
@ -95,7 +97,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
pw.Container(
width: double.infinity,
child: pw.Text(
restaurantInfo['nom']!,
restauranTitle,
style: pw.TextStyle(
fontSize: titleSize,
fontWeight: pw.FontWeight.bold,
@ -106,59 +108,15 @@ static Future<Uint8List> _generate58mmTicketPdf({
pw.SizedBox(height: 2),
// ADRESSE GAUCHE DÉCALÉE VERS LA GAUCHE (marginRight)
pw.Container(
width: double.infinity,
margin: const pw.EdgeInsets.only(right: 6),
child: pw.Text(
'Adresse: ${restaurantInfo['adresse']!}',
style: pw.TextStyle(fontSize: smallSize),
textAlign: pw.TextAlign.left,
),
),
// CONTACT GAUCHE DÉCALÉE
pw.Container(
width: double.infinity,
margin: const pw.EdgeInsets.only(right: 8),
child: pw.Text(
'Contact: ${restaurantInfo['contact']!}',
style: pw.TextStyle(fontSize: smallSize),
textAlign: pw.TextAlign.left,
),
),
// NIF GAUCHE DÉCALÉE
pw.Container(
width: double.infinity,
margin: const pw.EdgeInsets.only(right: 8),
margin: const pw.EdgeInsets.only(right: 2),
child: pw.Text(
'NIF: ${restaurantInfo['nif']!}',
formatTemplateContent(restaurantContent),
style: pw.TextStyle(fontSize: smallSize),
textAlign: pw.TextAlign.left,
),
),
// STAT GAUCHE DÉCALÉE
pw.Container(
width: double.infinity,
margin: const pw.EdgeInsets.only(right: 8),
child: pw.Text(
'STAT: ${restaurantInfo['stat']!}',
style: pw.TextStyle(fontSize: smallSize),
textAlign: pw.TextAlign.left,
),
),
pw.SizedBox(height: 3),
// Ligne de séparation
pw.Container(
width: double.infinity,
height: 0.5,
color: PdfColors.black,
),
pw.SizedBox(height: 2),
// FACTURE CENTRÉE
@ -353,6 +311,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
// Imprimer ticket 58mm
static Future<bool> printTicket({
required CommandeDetail commande,
required PrintTemplate template,
required String paymentMethod,
}) async {
try {
@ -363,6 +322,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
final pdfData = await _generate58mmTicketPdf(
commande: commande,
template: template,
paymentMethod: paymentMethod,
);
@ -385,6 +345,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
// Sauvegarder ticket 58mm
static Future<bool> saveTicketPdf({
required CommandeDetail commande,
required PrintTemplate template,
required String paymentMethod,
}) async {
try {
@ -393,6 +354,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
final pdfData = await _generate58mmTicketPdf(
commande: commande,
template: template,
paymentMethod: paymentMethod,
);
@ -434,19 +396,22 @@ static Future<Uint8List> _generate58mmTicketPdf({
// Méthodes pour compatibilité
static Future<bool> saveFacturePdf({
required CommandeDetail commande,
required PrintTemplate template,
required String paymentMethod,
}) async {
return await saveTicketPdf(
commande: commande,
template: template,
paymentMethod: paymentMethod,
);
}
static Future<bool> printFacture({
required CommandeDetail commande,
required PrintTemplate template,
required String paymentMethod,
}) async {
return await printTicket(commande: commande, paymentMethod: paymentMethod);
return await printTicket(commande: commande,template: template, paymentMethod: paymentMethod);
}
// Utilitaires de formatage

22
lib/services/restaurant_api_service.dart

@ -9,6 +9,8 @@ import 'package:itrimobe/models/command_detail.dart';
import 'package:itrimobe/models/payment_method.dart';
import 'package:itrimobe/models/tables_order.dart';
import '../pages/information.dart';
class RestaurantApiService {
static const String baseUrl = 'https://restaurant.careeracademy.mg';
@ -40,6 +42,24 @@ class RestaurantApiService {
}
}
static Future<PrintTemplate> getPrintTemplate() async {
final url = Uri.parse('$baseUrl/api/print-templates');
final response = await http.get(url);
if (response.statusCode == 200) {
final jsonResponse = json.decode(response.body);
final List templates = jsonResponse['data'];
if (templates.isEmpty) {
throw Exception("Aucun template trouvé");
}
return PrintTemplate.fromJson(templates.first);
} else {
throw Exception("Erreur lors de la récupération du template (${response.statusCode})");
}
}
// Récupérer les commandes
@ -58,7 +78,7 @@ class RestaurantApiService {
if (response.statusCode == 200) {
final dynamic responseBody = json.decode(response.body);
print('Réponse getCommandes: ${responseBody['data']['commandes']}');
// print('Réponse getCommandes: ${responseBody['data']['commandes']}');
// Validation de la structure de réponse
if (responseBody == null) {
throw const FormatException('Réponse vide du serveur');

45
lib/widgets/bottom_navigation.dart

@ -281,6 +281,51 @@ class AppBottomNavigation extends StatelessWidget {
),
),
const SizedBox(width: 20),
GestureDetector(
onTap: () => onItemTapped(7),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color:
selectedIndex == 7
? Colors.green.shade700
: Colors.transparent,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.payment,
color:
selectedIndex == 7
? Colors.white
: Colors.grey.shade600,
size: 16,
),
const SizedBox(width: 6),
Text(
'Information',
style: TextStyle(
color:
selectedIndex == 7
? Colors.white
: Colors.grey.shade600,
fontWeight:
selectedIndex == 7
? FontWeight.w500
: FontWeight.normal,
),
),
],
),
),
),
const Spacer(),
// User Profile Section

8
pubspec.lock

@ -168,6 +168,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.4"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev"
source: hosted
version: "0.18.1"
leak_tracker:
dependency: transitive
description:

Loading…
Cancel
Save