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 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 data; final Map pagination; ApiResponse({ required this.success, required this.data, required this.pagination, }); factory ApiResponse.fromJson(Map json) { var dataList = json['data'] as List; List 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 { List 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 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 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(); 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, ), ); } }