push
This commit is contained in:
parent
31c3d72a71
commit
b455feca42
@ -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,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,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,7 +116,8 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FactureScreen(
|
||||
commande: commande!,
|
||||
paymentMethod: selectedPaymentMethod!.id,
|
||||
template: template!,
|
||||
paymentMethod: selectedPaymentMethod!.id,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -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 ??
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
),
|
||||
Widget _buildHeader() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.template.title,
|
||||
style: const 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),
|
||||
),
|
||||
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,
|
||||
);
|
||||
|
||||
|
||||
@ -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
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 '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),
|
||||
margin: const pw.EdgeInsets.only(right: 2),
|
||||
child: pw.Text(
|
||||
'Adresse: ${restaurantInfo['adresse']!}',
|
||||
formatTemplateContent(restaurantContent),
|
||||
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),
|
||||
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),
|
||||
|
||||
// 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
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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…
Reference in New Issue
Block a user