push
This commit is contained in:
parent
31c3d72a71
commit
b455feca42
@ -38,6 +38,8 @@ class _MainLayoutState extends State<MainLayout> {
|
|||||||
return 5;
|
return 5;
|
||||||
case '/historique':
|
case '/historique':
|
||||||
return 6;
|
return 6;
|
||||||
|
case '/information':
|
||||||
|
return 7;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -71,6 +73,9 @@ class _MainLayoutState extends State<MainLayout> {
|
|||||||
case 6:
|
case 6:
|
||||||
route = '/historique';
|
route = '/historique';
|
||||||
break;
|
break;
|
||||||
|
case 7:
|
||||||
|
route = '/information';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
route = '/tables';
|
route = '/tables';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import 'pages/commandes_screen.dart';
|
|||||||
import 'pages/login_screen.dart';
|
import 'pages/login_screen.dart';
|
||||||
import 'pages/menus_screen.dart';
|
import 'pages/menus_screen.dart';
|
||||||
import 'pages/historique_commande.dart';
|
import 'pages/historique_commande.dart';
|
||||||
|
import 'pages/information.dart';
|
||||||
import 'pages/encaissement_screen.dart'; // NOUVEAU
|
import 'pages/encaissement_screen.dart'; // NOUVEAU
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -58,6 +59,11 @@ class MyApp extends StatelessWidget {
|
|||||||
currentRoute: '/historique',
|
currentRoute: '/historique',
|
||||||
child: OrderHistoryPage(),
|
child: OrderHistoryPage(),
|
||||||
),
|
),
|
||||||
|
'/information':
|
||||||
|
(context) => MainLayout(
|
||||||
|
currentRoute: '/information',
|
||||||
|
child: PrintTemplateManagementScreen(),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import '../models/payment_method.dart';
|
|||||||
import '../services/restaurant_api_service.dart';
|
import '../services/restaurant_api_service.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import 'information.dart';
|
||||||
|
|
||||||
class CaisseScreen extends StatefulWidget {
|
class CaisseScreen extends StatefulWidget {
|
||||||
final String commandeId;
|
final String commandeId;
|
||||||
final int tableNumber;
|
final int tableNumber;
|
||||||
@ -22,6 +24,7 @@ class CaisseScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _CaisseScreenState extends State<CaisseScreen> {
|
class _CaisseScreenState extends State<CaisseScreen> {
|
||||||
CommandeDetail? commande;
|
CommandeDetail? commande;
|
||||||
|
PrintTemplate? template;
|
||||||
PaymentMethod? selectedPaymentMethod;
|
PaymentMethod? selectedPaymentMethod;
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
bool isProcessingPayment = false;
|
bool isProcessingPayment = false;
|
||||||
@ -70,8 +73,10 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
final result = await RestaurantApiService.getCommandeDetails(
|
final result = await RestaurantApiService.getCommandeDetails(
|
||||||
widget.commandeId,
|
widget.commandeId,
|
||||||
);
|
);
|
||||||
|
final loadedTemplate = await RestaurantApiService.getPrintTemplate();
|
||||||
setState(() {
|
setState(() {
|
||||||
commande = result;
|
commande = result;
|
||||||
|
template = loadedTemplate;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -111,6 +116,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => FactureScreen(
|
builder: (context) => FactureScreen(
|
||||||
commande: commande!,
|
commande: commande!,
|
||||||
|
template: template!,
|
||||||
paymentMethod: selectedPaymentMethod!.id,
|
paymentMethod: selectedPaymentMethod!.id,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import 'package:itrimobe/services/pdf_service.dart';
|
|||||||
import '../layouts/main_layout.dart';
|
import '../layouts/main_layout.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import 'information.dart';
|
||||||
|
|
||||||
class CartPage extends StatefulWidget {
|
class CartPage extends StatefulWidget {
|
||||||
final int tableId;
|
final int tableId;
|
||||||
final int personne;
|
final int personne;
|
||||||
@ -462,12 +464,14 @@ class _CartPageState extends State<CartPage> {
|
|||||||
|
|
||||||
// Convertir le Map en objet CommandeDetail
|
// Convertir le Map en objet CommandeDetail
|
||||||
var commandeDetail = CommandeDetail.fromJson(commandeData['data']);
|
var commandeDetail = CommandeDetail.fromJson(commandeData['data']);
|
||||||
|
var template = PrintTemplate.fromJson(commandeData['data']);
|
||||||
|
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder:
|
builder:
|
||||||
(context) => FactureScreen(
|
(context) => FactureScreen(
|
||||||
commande: commandeDetail,
|
commande: commandeDetail,
|
||||||
|
template: template,
|
||||||
paymentMethod: selectedPaymentMethod!.id,
|
paymentMethod: selectedPaymentMethod!.id,
|
||||||
tablename:
|
tablename:
|
||||||
widget.tablename ??
|
widget.tablename ??
|
||||||
|
|||||||
@ -249,8 +249,7 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
|
|||||||
print('Error creating order: $e');
|
print('Error creating order: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Future<void> deleteOrder(Order order) async {
|
||||||
Future<void> deleteOrder(Order order) async {
|
|
||||||
try {
|
try {
|
||||||
final response = await http.delete(
|
final response = await http.delete(
|
||||||
Uri.parse('$baseUrl/commandes/${order.id}'),
|
Uri.parse('$baseUrl/commandes/${order.id}'),
|
||||||
@ -268,13 +267,20 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
|
|||||||
orders.remove(order);
|
orders.remove(order);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ✅ Mettre la table associée en "available"
|
||||||
|
await updateTableStatus(order.tableId, "available");
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text('Commande supprimée avec succès'),
|
content: Text('Commande supprimée avec succès'),
|
||||||
backgroundColor: Colors.green,
|
backgroundColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
throw Exception('Suppression échouée du côté serveur.');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Échec suppression: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@ -285,7 +291,8 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
|
|||||||
);
|
);
|
||||||
print('Error deleting order: $e');
|
print('Error deleting order: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
List<Order> get activeOrders {
|
List<Order> get activeOrders {
|
||||||
return orders
|
return orders
|
||||||
|
|||||||
@ -6,8 +6,10 @@ import 'package:flutter/services.dart';
|
|||||||
import '../models/command_detail.dart';
|
import '../models/command_detail.dart';
|
||||||
import '../services/pdf_service.dart';
|
import '../services/pdf_service.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'information.dart';
|
||||||
|
|
||||||
class FactureScreen extends StatefulWidget {
|
class FactureScreen extends StatefulWidget {
|
||||||
|
final PrintTemplate template;
|
||||||
final CommandeDetail commande;
|
final CommandeDetail commande;
|
||||||
final String paymentMethod;
|
final String paymentMethod;
|
||||||
final String? tablename;
|
final String? tablename;
|
||||||
@ -17,6 +19,7 @@ class FactureScreen extends StatefulWidget {
|
|||||||
required this.commande,
|
required this.commande,
|
||||||
required this.paymentMethod,
|
required this.paymentMethod,
|
||||||
this.tablename,
|
this.tablename,
|
||||||
|
required this.template,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -38,9 +41,14 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String get factureNumber {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -117,36 +125,28 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader() {
|
||||||
return const Column(
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'RESTAURANT ITRIMOBE',
|
widget.template.title,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
letterSpacing: 1.2,
|
letterSpacing: 1.2,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Adresse: Moramanga, Madagascar',
|
formatTemplateContent(widget.template.content),
|
||||||
style: TextStyle(fontSize: 12, color: Colors.black87),
|
style: const TextStyle(fontSize: 12, color: Colors.black87),
|
||||||
),
|
textAlign: TextAlign.center,
|
||||||
Text(
|
|
||||||
'Contact: +261 34 12 34 56',
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.black87),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'NIF: 4002141594',
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.black87),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'STAT: 10715 33 2025 0 00414',
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.black87),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Widget _buildFactureInfo() {
|
Widget _buildFactureInfo() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
@ -333,11 +333,13 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
if (action == 'print') {
|
if (action == 'print') {
|
||||||
success = await PlatformPrintService.printFacture(
|
success = await PlatformPrintService.printFacture(
|
||||||
commande: widget.commande,
|
commande: widget.commande,
|
||||||
|
template: widget.template,
|
||||||
paymentMethod: widget.paymentMethod,
|
paymentMethod: widget.paymentMethod,
|
||||||
);
|
);
|
||||||
} else if (action == 'save') {
|
} else if (action == 'save') {
|
||||||
success = await PlatformPrintService.saveFacturePdf(
|
success = await PlatformPrintService.saveFacturePdf(
|
||||||
commande: widget.commande,
|
commande: widget.commande,
|
||||||
|
template: widget.template,
|
||||||
paymentMethod: widget.paymentMethod,
|
paymentMethod: widget.paymentMethod,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -387,6 +389,7 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
if (shouldSave == true) {
|
if (shouldSave == true) {
|
||||||
final success = await PlatformPrintService.saveFacturePdf(
|
final success = await PlatformPrintService.saveFacturePdf(
|
||||||
commande: widget.commande,
|
commande: widget.commande,
|
||||||
|
template: widget.template,
|
||||||
paymentMethod: widget.paymentMethod,
|
paymentMethod: widget.paymentMethod,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -54,34 +54,19 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
|
|
||||||
final response = await http.get(uri, headers: _headers);
|
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) {
|
if (response.statusCode == 200) {
|
||||||
final dynamic responseBody = json.decode(response.body);
|
final dynamic responseBody = json.decode(response.body);
|
||||||
print('=== PARSED RESPONSE ===');
|
|
||||||
print('Type: ${responseBody.runtimeType}');
|
|
||||||
print('Content: $responseBody');
|
|
||||||
|
|
||||||
List<dynamic> data = [];
|
List<dynamic> data = [];
|
||||||
|
|
||||||
// Gestion améliorée de la réponse
|
// Gestion améliorée de la réponse
|
||||||
if (responseBody is Map<String, dynamic>) {
|
if (responseBody is Map<String, dynamic>) {
|
||||||
print('=== RESPONSE EST UN MAP ===');
|
|
||||||
print('Keys disponibles: ${responseBody.keys.toList()}');
|
|
||||||
|
|
||||||
// Structure: {"success": true, "data": {"commandes": [...], "pagination": {...}}}
|
// Structure: {"success": true, "data": {"commandes": [...], "pagination": {...}}}
|
||||||
if (responseBody.containsKey('data') && responseBody['data'] is Map<String, dynamic>) {
|
if (responseBody.containsKey('data') && responseBody['data'] is Map<String, dynamic>) {
|
||||||
final dataMap = responseBody['data'] as 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')) {
|
if (dataMap.containsKey('commandes')) {
|
||||||
final commandesValue = dataMap['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>) {
|
if (commandesValue is List<dynamic>) {
|
||||||
data = commandesValue;
|
data = commandesValue;
|
||||||
@ -96,8 +81,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
currentPage = pagination['currentPage'] ?? page;
|
currentPage = pagination['currentPage'] ?? page;
|
||||||
totalPages = pagination['totalPages'] ?? 1;
|
totalPages = pagination['totalPages'] ?? 1;
|
||||||
totalItems = pagination['totalItems'] ?? data.length;
|
totalItems = pagination['totalItems'] ?? data.length;
|
||||||
print('=== PAGINATION ===');
|
|
||||||
print('Page: $currentPage/$totalPages, Total: $totalItems');
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Si pas de pagination dans la réponse, calculer approximativement
|
// Si pas de pagination dans la réponse, calculer approximativement
|
||||||
@ -106,7 +89,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
totalPages = (totalItems / itemsPerPage).ceil();
|
totalPages = (totalItems / itemsPerPage).ceil();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print('=== PAS DE COMMANDES DANS DATA ===');
|
|
||||||
totalItems = 0;
|
totalItems = 0;
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
totalPages = 1;
|
totalPages = 1;
|
||||||
@ -114,7 +96,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
} else if (responseBody.containsKey('commandes')) {
|
} else if (responseBody.containsKey('commandes')) {
|
||||||
// Fallback: commandes directement dans responseBody
|
// Fallback: commandes directement dans responseBody
|
||||||
final commandesValue = responseBody['commandes'];
|
final commandesValue = responseBody['commandes'];
|
||||||
print('=== COMMANDES DIRECTES ===');
|
|
||||||
|
|
||||||
if (commandesValue is List<dynamic>) {
|
if (commandesValue is List<dynamic>) {
|
||||||
data = commandesValue;
|
data = commandesValue;
|
||||||
@ -125,14 +106,11 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
currentPage = page;
|
currentPage = page;
|
||||||
totalPages = (totalItems / itemsPerPage).ceil();
|
totalPages = (totalItems / itemsPerPage).ceil();
|
||||||
} else {
|
} else {
|
||||||
print('=== STRUCTURE INCONNUE ===');
|
|
||||||
print('Clés disponibles: ${responseBody.keys.toList()}');
|
|
||||||
totalItems = 0;
|
totalItems = 0;
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
totalPages = 1;
|
totalPages = 1;
|
||||||
}
|
}
|
||||||
} else if (responseBody is List<dynamic>) {
|
} else if (responseBody is List<dynamic>) {
|
||||||
print('=== RESPONSE EST UNE LISTE ===');
|
|
||||||
data = responseBody;
|
data = responseBody;
|
||||||
totalItems = data.length;
|
totalItems = data.length;
|
||||||
currentPage = page;
|
currentPage = page;
|
||||||
@ -141,47 +119,22 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}');
|
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
|
// Conversion sécurisée avec prints détaillés
|
||||||
List<CommandeData> parsedCommandes = [];
|
List<CommandeData> parsedCommandes = [];
|
||||||
for (int i = 0; i < data.length; i++) {
|
for (int i = 0; i < data.length; i++) {
|
||||||
try {
|
try {
|
||||||
final item = data[i];
|
final item = data[i];
|
||||||
print('=== ITEM $i ===');
|
|
||||||
print('Type: ${item.runtimeType}');
|
|
||||||
print('Contenu complet: $item');
|
|
||||||
|
|
||||||
if (item is Map<String, dynamic>) {
|
if (item is Map<String, dynamic>) {
|
||||||
print('--- ANALYSE DES CHAMPS ---');
|
|
||||||
item.forEach((key, value) {
|
item.forEach((key, value) {
|
||||||
print('$key: $value (${value.runtimeType})');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final commandeData = CommandeData.fromJson(item);
|
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) {
|
if (commandeData.items != null) {
|
||||||
print('--- ITEMS DE LA COMMANDE ---');
|
|
||||||
for (int j = 0; j < commandeData.items!.length; j++) {
|
for (int j = 0; j < commandeData.items!.length; j++) {
|
||||||
final commandeItem = commandeData.items![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(() {
|
setState(() {
|
||||||
commandes = parsedCommandes;
|
commandes = parsedCommandes;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@ -1081,8 +1031,6 @@ class CommandeData {
|
|||||||
items: items,
|
items: items,
|
||||||
tablename: tablename,
|
tablename: tablename,
|
||||||
);
|
);
|
||||||
|
|
||||||
print('=== COMMANDE PARSÉE AVEC SUCCÈS ===');
|
|
||||||
return result;
|
return result;
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('=== ERREUR PARSING COMMANDE ===');
|
print('=== ERREUR PARSING COMMANDE ===');
|
||||||
@ -1109,7 +1057,6 @@ class CommandeData {
|
|||||||
if (value is String) {
|
if (value is String) {
|
||||||
try {
|
try {
|
||||||
final result = DateTime.parse(value);
|
final result = DateTime.parse(value);
|
||||||
print('String to datetime: "$value" -> $result');
|
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur parsing date: $value - $e');
|
print('Erreur parsing date: $value - $e');
|
||||||
@ -1121,8 +1068,6 @@ class CommandeData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static List<CommandeItem>? _parseItems(dynamic value) {
|
static List<CommandeItem>? _parseItems(dynamic value) {
|
||||||
print('=== PARSING ITEMS ===');
|
|
||||||
print('Items bruts: $value (${value.runtimeType})');
|
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
print('Items null');
|
print('Items null');
|
||||||
@ -1137,20 +1082,14 @@ class CommandeData {
|
|||||||
try {
|
try {
|
||||||
List<CommandeItem> result = [];
|
List<CommandeItem> result = [];
|
||||||
for (int i = 0; i < value.length; i++) {
|
for (int i = 0; i < value.length; i++) {
|
||||||
print('--- ITEM $i ---');
|
|
||||||
final item = value[i];
|
final item = value[i];
|
||||||
print('Item brut: $item (${item.runtimeType})');
|
|
||||||
|
|
||||||
if (item is Map<String, dynamic>) {
|
if (item is Map<String, dynamic>) {
|
||||||
final commandeItem = CommandeItem.fromJson(item);
|
final commandeItem = CommandeItem.fromJson(item);
|
||||||
result.add(commandeItem);
|
result.add(commandeItem);
|
||||||
print('Item parsé: ${commandeItem.menuNom}');
|
|
||||||
} else {
|
} else {
|
||||||
print('Item $i n\'est pas un Map');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print('Total items parsés: ${result.length}');
|
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur parsing items: $e');
|
print('Erreur parsing items: $e');
|
||||||
@ -1208,32 +1147,23 @@ class CommandeItem {
|
|||||||
|
|
||||||
factory CommandeItem.fromJson(Map<String, dynamic> json) {
|
factory CommandeItem.fromJson(Map<String, dynamic> json) {
|
||||||
try {
|
try {
|
||||||
print('=== PARSING COMMANDE ITEM ===');
|
|
||||||
print('JSON item: $json');
|
|
||||||
|
|
||||||
// Debug chaque champ
|
// Debug chaque champ
|
||||||
final id = json['id'] ?? 0;
|
final id = json['id'] ?? 0;
|
||||||
print('ID: ${json['id']} -> $id');
|
|
||||||
|
|
||||||
final commandeId = json['commande_id'] ?? 0;
|
final commandeId = json['commande_id'] ?? 0;
|
||||||
print('Commande ID: ${json['commande_id']} -> $commandeId');
|
|
||||||
|
|
||||||
final menuId = json['menu_id'] ?? 0;
|
final menuId = json['menu_id'] ?? 0;
|
||||||
print('Menu ID: ${json['menu_id']} -> $menuId');
|
|
||||||
|
|
||||||
final quantite = json['quantite'] ?? json['quantity'] ?? 0;
|
final quantite = json['quantite'] ?? json['quantity'] ?? 0;
|
||||||
print('Quantité: ${json['quantite']} / ${json['quantity']} -> $quantite');
|
|
||||||
|
|
||||||
final prixUnitaire = CommandeData._parseDouble(json['prix_unitaire']) ??
|
final prixUnitaire = CommandeData._parseDouble(json['prix_unitaire']) ??
|
||||||
CommandeData._parseDouble(json['unit_price']) ?? 0.0;
|
CommandeData._parseDouble(json['unit_price']) ?? 0.0;
|
||||||
print('Prix unitaire: ${json['prix_unitaire']} / ${json['unit_price']} -> $prixUnitaire');
|
|
||||||
|
|
||||||
final totalItem = CommandeData._parseDouble(json['total_item']) ??
|
final totalItem = CommandeData._parseDouble(json['total_item']) ??
|
||||||
CommandeData._parseDouble(json['total']) ?? 0.0;
|
CommandeData._parseDouble(json['total']) ?? 0.0;
|
||||||
print('Total item: ${json['total_item']} / ${json['total']} -> $totalItem');
|
|
||||||
|
|
||||||
final commentaires = json['commentaires']?.toString() ?? json['comments']?.toString();
|
final commentaires = json['commentaires']?.toString() ?? json['comments']?.toString();
|
||||||
print('Commentaires: ${json['commentaires']} / ${json['comments']} -> $commentaires');
|
|
||||||
|
|
||||||
final statut = json['statut']?.toString() ?? json['status']?.toString() ?? '';
|
final statut = json['statut']?.toString() ?? json['status']?.toString() ?? '';
|
||||||
final menuNom = json['menu_nom']?.toString() ??
|
final menuNom = json['menu_nom']?.toString() ??
|
||||||
@ -1242,15 +1172,12 @@ class CommandeItem {
|
|||||||
|
|
||||||
final menuDescription = json['menu_description']?.toString() ??
|
final menuDescription = json['menu_description']?.toString() ??
|
||||||
json['description']?.toString() ?? '';
|
json['description']?.toString() ?? '';
|
||||||
print('Menu description: ${json['menu_description']} / ${json['description']} -> $menuDescription');
|
|
||||||
|
|
||||||
final menuPrixActuel = CommandeData._parseDouble(json['menu_prix_actuel']) ??
|
final menuPrixActuel = CommandeData._parseDouble(json['menu_prix_actuel']) ??
|
||||||
CommandeData._parseDouble(json['current_price']) ?? 0.0;
|
CommandeData._parseDouble(json['current_price']) ?? 0.0;
|
||||||
print('Menu prix actuel: ${json['menu_prix_actuel']} / ${json['current_price']} -> $menuPrixActuel');
|
|
||||||
|
|
||||||
final tablename = json['tablename']?.toString() ??
|
final tablename = json['tablename']?.toString() ??
|
||||||
json['table_name']?.toString() ?? '';
|
json['table_name']?.toString() ?? '';
|
||||||
print('Table name: ${json['tablename']} / ${json['table_name']} -> $tablename');
|
|
||||||
|
|
||||||
final result = CommandeItem(
|
final result = CommandeItem(
|
||||||
id: id,
|
id: id,
|
||||||
|
|||||||
467
lib/pages/information.dart
Normal file
467
lib/pages/information.dart
Normal file
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,8 @@ import 'package:permission_handler/permission_handler.dart';
|
|||||||
import '../models/command_detail.dart';
|
import '../models/command_detail.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../pages/information.dart';
|
||||||
|
|
||||||
class PlatformPrintService {
|
class PlatformPrintService {
|
||||||
// Format spécifique 58mm pour petites imprimantes - CENTRÉ POUR L'IMPRESSION
|
// Format spécifique 58mm pour petites imprimantes - CENTRÉ POUR L'IMPRESSION
|
||||||
static const PdfPageFormat ticket58mmFormat = PdfPageFormat(
|
static const PdfPageFormat ticket58mmFormat = PdfPageFormat(
|
||||||
@ -44,6 +46,7 @@ class PlatformPrintService {
|
|||||||
// Générer PDF optimisé pour 58mm - VERSION IDENTIQUE À L'ÉCRAN
|
// Générer PDF optimisé pour 58mm - VERSION IDENTIQUE À L'ÉCRAN
|
||||||
static Future<Uint8List> _generate58mmTicketPdf({
|
static Future<Uint8List> _generate58mmTicketPdf({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
|
required PrintTemplate template,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
final pdf = pw.Document();
|
final pdf = pw.Document();
|
||||||
@ -51,16 +54,12 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
const double titleSize = 8;
|
const double titleSize = 8;
|
||||||
const double headerSize = 8;
|
const double headerSize = 8;
|
||||||
const double bodySize = 7;
|
const double bodySize = 7;
|
||||||
const double smallSize = 6;
|
const double smallSize = 5;
|
||||||
const double lineHeight = 1.2;
|
const double lineHeight = 1.2;
|
||||||
|
|
||||||
final restaurantInfo = {
|
final restauranTitle = template.title ?? 'Nom du restaurant';
|
||||||
'nom': 'RESTAURANT ITRIMOBE',
|
final restaurantContent = template.content ?? 'Adresse inconnue';
|
||||||
'adresse': 'Moramanga, Madagascar',
|
|
||||||
'contact': '+261 34 12 34 56',
|
|
||||||
'nif': '4002141594',
|
|
||||||
'stat': '10715 33 2025 0 00414',
|
|
||||||
};
|
|
||||||
|
|
||||||
final factureNumber = 'F${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
|
final factureNumber = 'F${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
|
||||||
final dateTime = DateTime.now();
|
final dateTime = DateTime.now();
|
||||||
@ -79,6 +78,9 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
default:
|
default:
|
||||||
paymentMethodText = 'CB';
|
paymentMethodText = 'CB';
|
||||||
}
|
}
|
||||||
|
String formatTemplateContent(String content) {
|
||||||
|
return content.replaceAll('\\r\\n', '\n').replaceAll('\\n', '\n');
|
||||||
|
}
|
||||||
|
|
||||||
pdf.addPage(
|
pdf.addPage(
|
||||||
pw.Page(
|
pw.Page(
|
||||||
@ -95,7 +97,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
restaurantInfo['nom']!,
|
restauranTitle,
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: titleSize,
|
fontSize: titleSize,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
@ -106,59 +108,15 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
|
|
||||||
pw.SizedBox(height: 2),
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// ADRESSE GAUCHE DÉCALÉE VERS LA GAUCHE (marginRight)
|
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
margin: const pw.EdgeInsets.only(right: 6),
|
margin: const pw.EdgeInsets.only(right: 2),
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
'Adresse: ${restaurantInfo['adresse']!}',
|
formatTemplateContent(restaurantContent),
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
textAlign: pw.TextAlign.left,
|
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),
|
|
||||||
child: pw.Text(
|
|
||||||
'NIF: ${restaurantInfo['nif']!}',
|
|
||||||
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),
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// FACTURE CENTRÉE
|
// FACTURE CENTRÉE
|
||||||
@ -353,6 +311,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
// Imprimer ticket 58mm
|
// Imprimer ticket 58mm
|
||||||
static Future<bool> printTicket({
|
static Future<bool> printTicket({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
|
required PrintTemplate template,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
@ -363,6 +322,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
|
|
||||||
final pdfData = await _generate58mmTicketPdf(
|
final pdfData = await _generate58mmTicketPdf(
|
||||||
commande: commande,
|
commande: commande,
|
||||||
|
template: template,
|
||||||
paymentMethod: paymentMethod,
|
paymentMethod: paymentMethod,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -385,6 +345,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
// Sauvegarder ticket 58mm
|
// Sauvegarder ticket 58mm
|
||||||
static Future<bool> saveTicketPdf({
|
static Future<bool> saveTicketPdf({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
|
required PrintTemplate template,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
@ -393,6 +354,7 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
|
|
||||||
final pdfData = await _generate58mmTicketPdf(
|
final pdfData = await _generate58mmTicketPdf(
|
||||||
commande: commande,
|
commande: commande,
|
||||||
|
template: template,
|
||||||
paymentMethod: paymentMethod,
|
paymentMethod: paymentMethod,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -434,19 +396,22 @@ static Future<Uint8List> _generate58mmTicketPdf({
|
|||||||
// Méthodes pour compatibilité
|
// Méthodes pour compatibilité
|
||||||
static Future<bool> saveFacturePdf({
|
static Future<bool> saveFacturePdf({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
|
required PrintTemplate template,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
return await saveTicketPdf(
|
return await saveTicketPdf(
|
||||||
commande: commande,
|
commande: commande,
|
||||||
|
template: template,
|
||||||
paymentMethod: paymentMethod,
|
paymentMethod: paymentMethod,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> printFacture({
|
static Future<bool> printFacture({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
|
required PrintTemplate template,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
return await printTicket(commande: commande, paymentMethod: paymentMethod);
|
return await printTicket(commande: commande,template: template, paymentMethod: paymentMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilitaires de formatage
|
// Utilitaires de formatage
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import 'package:itrimobe/models/command_detail.dart';
|
|||||||
import 'package:itrimobe/models/payment_method.dart';
|
import 'package:itrimobe/models/payment_method.dart';
|
||||||
import 'package:itrimobe/models/tables_order.dart';
|
import 'package:itrimobe/models/tables_order.dart';
|
||||||
|
|
||||||
|
import '../pages/information.dart';
|
||||||
|
|
||||||
class RestaurantApiService {
|
class RestaurantApiService {
|
||||||
static const String baseUrl = 'https://restaurant.careeracademy.mg';
|
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
|
// Récupérer les commandes
|
||||||
@ -58,7 +78,7 @@ class RestaurantApiService {
|
|||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
final dynamic responseBody = json.decode(response.body);
|
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
|
// Validation de la structure de réponse
|
||||||
if (responseBody == null) {
|
if (responseBody == null) {
|
||||||
throw const FormatException('Réponse vide du serveur');
|
throw const FormatException('Réponse vide du serveur');
|
||||||
|
|||||||
@ -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(),
|
const Spacer(),
|
||||||
|
|
||||||
// User Profile Section
|
// User Profile Section
|
||||||
|
|||||||
@ -168,6 +168,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.4"
|
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:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user