import 'dart:io'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:path_provider/path_provider.dart'; import 'package:flutter/services.dart'; // For rootBundle import 'package:shopbillingsoftware/Plans_Packages/reuseable_prints/Prints language/english_print.dart'; import 'package:shopbillingsoftware/Plans_Packages/reuseable_prints/Prints language/tamil_print.dart'; import 'package:shopbillingsoftware/Plans_Packages/reuseable_prints/Prints language/Hindi_print.dart'; import 'package:shopbillingsoftware/Plans_Packages/reuseable_prints/Prints language/malayalam_print.dart'; // Add barcode scanner package - you'll need to add this to your pubspec.yaml import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart'; class PremiumSalesComponent extends StatefulWidget { final Map userData; const PremiumSalesComponent({Key? key, required this.userData}) : super(key: key); @override _SalesComponentState createState() => _SalesComponentState(); } class _SalesComponentState extends State { List _products = []; Map _businessProfile = {}; Map _settings = {}; bool _isLoading = false; bool _isProfileLoading = true; bool _isSettingsLoading = true; // 🔵 LITE BLUE COLOR SCHEME final Color _primaryColor = Colors.green; final Color _primaryDark = Colors.green[800]!; final Color _primaryLight = Colors.green[50]!; final Color _backgroundColor = Color(0xFFF7FDF7); final Color _cardColor = Colors.white; final Color _textColor = Color(0xFF3B82F6); final Color _textLight = Color(0xFF6B7280); final String _productsUrl = 'http://localhost/shop_billing/Admin_register/products.php'; final String _profileUrl = 'http://localhost/shop_billing/Admin_register/'; final String _settingsUrl = 'http://localhost/shop_billing/Admin_register/settings.php'; @override void initState() { super.initState(); _fetchBusinessProfile(); _fetchSettings(); _fetchProducts(); } Future _fetchBusinessProfile() async { setState(() => _isProfileLoading = true); try { final response = await http.get( Uri.parse("${_profileUrl}get_profile.php?admin_id=${widget.userData['id']}"), ); if (response.statusCode == 200) { final dynamic json = jsonDecode(response.body); if (json['success'] && json['data'] != null) { setState(() { _businessProfile = Map.from(json['data']); }); print('Business profile loaded: ${_businessProfile['business_name']}'); print('Signature path: ${_businessProfile['signature']}'); } } } catch (e) { print("Profile fetch error: $e"); } finally { setState(() => _isProfileLoading = false); } } Future _fetchSettings() async { setState(() => _isSettingsLoading = true); try { final response = await http.get( Uri.parse('$_settingsUrl?action=get_settings&admin_id=${widget.userData['id']}'), ); if (response.statusCode == 200) { final dynamic json = jsonDecode(response.body); print('Settings API Response: $json'); if (json['status'] == 'success' && json['settings'] != null) { setState(() { _settings = Map.from(json['settings']); }); print('Settings loaded successfully: $_settings'); } else { _setDefaultSettings(); } } else { print('Settings API Error: ${response.statusCode}'); _setDefaultSettings(); } } catch (e) { print("Settings fetch error: $e"); _setDefaultSettings(); } finally { setState(() => _isSettingsLoading = false); } } void _setDefaultSettings() { setState(() { _settings = { 'receipt_type': 'thermal', 'paper_size': '80mm', 'print_with_logo': '1', 'print_with_signature': '1', 'gst_type': 'with_gst', 'language': 'english', 'fssai_number': '', 'gstin_number': '', }; }); print('Default settings set: $_settings'); } // 🔥 Filter products to show only those with same admin_id List get _filteredProductsByAdmin { final currentAdminId = int.tryParse(widget.userData['id']?.toString() ?? '1') ?? 1; return _products.where((product) { final productAdminId = int.tryParse(product['admin_id']?.toString() ?? '0') ?? 0; return productAdminId == currentAdminId; }).toList(); } Future _fetchProducts() async { setState(() { _isLoading = true; }); try { print('🔄 Fetching products from: $_productsUrl'); final response = await http.get(Uri.parse(_productsUrl)); print('📡 Response status: ${response.statusCode}'); if (response.statusCode == 200) { List productData = json.decode(response.body); print('📦 Products fetched: ${productData.length}'); // Debug: Print first few products if (productData.isNotEmpty) { for (int i = 0; i < (productData.length > 3 ? 3 : productData.length); i++) { print('Product $i: ${productData[i]}'); } } setState(() { _products = productData; }); // Check filtered products print(' Filtered products by admin: ${_filteredProductsByAdmin.length}'); } else { print(' Failed to fetch products. Status: ${response.statusCode}'); print('Response body: ${response.body}'); } } catch (e) { print(' Error fetching products: $e'); } finally { setState(() { _isLoading = false; }); } } void _showSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), duration: Duration(seconds: 3), backgroundColor: _primaryColor, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: _backgroundColor, body: _isLoading || _isProfileLoading || _isSettingsLoading ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(color: _primaryColor, strokeWidth: 3), SizedBox(height: 16), Text( 'Loading...', style: TextStyle(color: _textLight, fontSize: 16), ), ], ), ) : NewSalePage( userData: widget.userData, products: _filteredProductsByAdmin, businessProfile: _businessProfile, settings: _settings, baseUrl: _profileUrl, onSettingsUpdated: _fetchSettings, ), ); } } // New Sale Page with Lite Blue UI class NewSalePage extends StatefulWidget { final Map userData; final List products; final Map businessProfile; final Map settings; final String baseUrl; final VoidCallback? onSettingsUpdated; const NewSalePage({ Key? key, required this.userData, required this.products, required this.businessProfile, required this.settings, required this.baseUrl, this.onSettingsUpdated, }) : super(key: key); @override _NewSalePageState createState() => _NewSalePageState(); } class _NewSalePageState extends State { final _formKey = GlobalKey(); // Controllers TextEditingController invoiceController = TextEditingController(); TextEditingController customerNameController = TextEditingController(); TextEditingController mobileNumberController = TextEditingController(); TextEditingController saleDateController = TextEditingController(); TextEditingController tenderAmountController = TextEditingController(); // Product list List> saleItems = []; // Focus nodes for keyboard navigation List _productFocusNodes = []; List _quantityFocusNodes = []; // Calculations double totalTaxableValue = 0.0; double totalGSTAmount = 0.0; double totalCGSTAmount = 0.0; double totalSGSTAmount = 0.0; double totalDiscount = 0.0; double grandTotal = 0.0; double tenderAmount = 0.0; double balanceAmount = 0.0; bool _isSaving = false; String _selectedLanguage = 'english'; // Default language // Barcode scanning bool _isScanning = false; String _lastScannedBarcode = ''; // 🔵 LITE BLUE COLOR SCHEME final Color _primaryColor = Color(0xFF3B82F6); final Color _primaryDark = Color(0xFF1976D2); final Color _primaryLight = Color(0xFFE3F2FD); final Color _backgroundColor = Color(0xFFF8FBFF); final Color _cardColor = Colors.white; final Color _textColor = Color(0xFF1F2937); final Color _textLight = Color(0xFF6B7280); final String _salesUrl = 'http://localhost/shop_billing/Admin_register/sales.php'; // Dynamic Settings Getters String get _gstType => widget.settings['gst_type']?.toString().toLowerCase() ?? 'with_gst'; String get _language => widget.settings['language']?.toString().toLowerCase() ?? 'english'; String get _receiptType => widget.settings['receipt_type']?.toString().toLowerCase() ?? 'thermal'; String get _paperSize => widget.settings['paper_size']?.toString().toLowerCase() ?? '80mm'; bool get _printWithLogo => widget.settings['print_with_logo']?.toString() == '1'; bool get _printWithSignature => widget.settings['print_with_signature']?.toString() == '1' && _printWithLogo; // 🔥 MODIFIED: Signature depends on logo String get _fssaiNumber => widget.settings['fssai_number']?.toString() ?? ''; String get _gstinNumber => widget.settings['gstin_number']?.toString() ?? ''; @override void initState() { super.initState(); _initializeForm(); _initializeFocusNodes(); // Listen to tender amount changes tenderAmountController.addListener(_calculateBalance); } void _initializeForm() { // Generate invoice number invoiceController.text = 'SALE-${DateTime.now().millisecondsSinceEpoch}'; // Set current date saleDateController.text = _getCurrentDate(); // Clear tender amount tenderAmountController.clear(); // Reset product items saleItems.clear(); _addProductItem(); // Reset calculations totalTaxableValue = 0.0; totalGSTAmount = 0.0; totalCGSTAmount = 0.0; totalSGSTAmount = 0.0; totalDiscount = 0.0; grandTotal = 0.0; tenderAmount = 0.0; balanceAmount = 0.0; // Set language from settings _selectedLanguage = _language; // Debug settings print('Form initialized with new invoice: ${invoiceController.text}'); } void _initializeFocusNodes() { // Initialize focus nodes for existing items for (int i = 0; i < saleItems.length; i++) { _productFocusNodes.add(FocusNode()); _quantityFocusNodes.add(FocusNode()); } } String _getCurrentDate() { final now = DateTime.now(); return '${now.day.toString().padLeft(2, '0')}-${now.month.toString().padLeft(2, '0')}-${now.year}'; } void _addProductItem() { setState(() { saleItems.add({ 'product_id': 0, 'quantity': 1, 'price_incl_gst': 0.0, 'discount': 0.0, 'gst_percentage': 0.0, 'taxable_amount': 0.0, 'total_amount': 0.0, 'cgst_amount': 0.0, 'sgst_amount': 0.0, 'product_name': '', 'barcode': '', }); // Add focus nodes for new item _productFocusNodes.add(FocusNode()); _quantityFocusNodes.add(FocusNode()); }); } void _removeProductItem(int index) { if (saleItems.length > 1) { setState(() { saleItems.removeAt(index); _productFocusNodes.removeAt(index); _quantityFocusNodes.removeAt(index); _calculateTotals(); }); } } void _updateProductItem(int index, String field, dynamic value) { setState(() { saleItems[index][field] = value; _calculateProductItem(index); _calculateTotals(); }); } void _calculateProductItem(int index) { int quantity = saleItems[index]['quantity']; double price = saleItems[index]['price_incl_gst']; double discount = saleItems[index]['discount']; // Use GST percentage only if GST is enabled, otherwise set to 0 double gstRate = _gstType == 'with_gst' ? saleItems[index]['gst_percentage'] : 0.0; // Calculate discounted price double discountedPrice = price - (price * discount / 100); // Calculate taxable amount (before GST) double taxableAmount = discountedPrice * quantity; // Calculate GST amount (0 if GST is disabled) double gstAmount = _gstType == 'with_gst' ? taxableAmount * gstRate / 100 : 0.0; double cgstAmount = gstAmount / 2; double sgstAmount = gstAmount / 2; // Calculate total amount (taxable + GST) double totalAmount = taxableAmount + gstAmount; setState(() { saleItems[index]['taxable_amount'] = double.parse(taxableAmount.toStringAsFixed(2)); saleItems[index]['total_amount'] = double.parse(totalAmount.toStringAsFixed(2)); saleItems[index]['gst_percentage'] = gstRate; saleItems[index]['cgst_amount'] = double.parse(cgstAmount.toStringAsFixed(2)); saleItems[index]['sgst_amount'] = double.parse(sgstAmount.toStringAsFixed(2)); }); } void _calculateTotals() { totalTaxableValue = 0.0; totalGSTAmount = 0.0; totalCGSTAmount = 0.0; totalSGSTAmount = 0.0; totalDiscount = 0.0; grandTotal = 0.0; for (var item in saleItems) { totalTaxableValue += item['taxable_amount']; // Only calculate GST amount if GST is enabled if (_gstType == 'with_gst') { totalGSTAmount += item['taxable_amount'] * item['gst_percentage'] / 100; totalCGSTAmount += (item['taxable_amount'] * item['gst_percentage'] / 100) / 2; totalSGSTAmount += (item['taxable_amount'] * item['gst_percentage'] / 100) / 2; } totalDiscount += (item['price_incl_gst'] * item['discount'] / 100) * item['quantity']; } grandTotal = totalTaxableValue + totalGSTAmount; // Round to 2 decimal places totalTaxableValue = double.parse(totalTaxableValue.toStringAsFixed(2)); totalGSTAmount = double.parse(totalGSTAmount.toStringAsFixed(2)); totalCGSTAmount = double.parse(totalCGSTAmount.toStringAsFixed(2)); totalSGSTAmount = double.parse(totalSGSTAmount.toStringAsFixed(2)); totalDiscount = double.parse(totalDiscount.toStringAsFixed(2)); grandTotal = double.parse(grandTotal.toStringAsFixed(2)); // Recalculate balance when totals change _calculateBalance(); } void _calculateBalance() { double tender = double.tryParse(tenderAmountController.text) ?? 0.0; setState(() { tenderAmount = tender; balanceAmount = tender - grandTotal; }); } // 🔥 BARCODE SCANNING FUNCTIONALITY Future _scanBarcode(int index) async { if (_isScanning) return; setState(() { _isScanning = true; }); try { String barcodeScanRes = await FlutterBarcodeScanner.scanBarcode( '#FF3B82F6', // Scanner color 'Cancel', // Cancel button text true, // Show flash option ScanMode.BARCODE, // Scan mode ); if (barcodeScanRes != '-1') { _lastScannedBarcode = barcodeScanRes; _findProductByBarcode(barcodeScanRes, index); } else { _showSnackBar('Scanning cancelled'); } } on PlatformException { _showSnackBar('Failed to scan barcode'); } catch (e) { _showSnackBar('Error: $e'); } finally { setState(() { _isScanning = false; }); } } void _findProductByBarcode(String barcode, int index) { // Search for product by barcode in the products list Map? foundProduct; for (var product in widget.products) { String productBarcode = product['barcode']?.toString() ?? ''; if (productBarcode.isNotEmpty && productBarcode == barcode) { foundProduct = Map.from(product); break; } } if (foundProduct != null) { _onProductSelected(index, foundProduct); _showSnackBar('Product found: ${foundProduct['product_name']}'); } else { _showSnackBar('Product not found for barcode: $barcode'); // Optionally, you can manually search for the product _showManualSearchDialog(barcode, index); } } void _showManualSearchDialog(String barcode, int index) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Text('Product Not Found'), content: Text('No product found with barcode: $barcode\n\nPlease select the product manually.'), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); // Focus on the search field for manual selection Future.delayed(Duration(milliseconds: 300), () { if (_productFocusNodes.length > index) { _productFocusNodes[index].requestFocus(); } }); }, child: Text('OK'), ), ], ); }, ); } // 🔥 MODIFIED: Enhanced product selection with auto-navigation void _onProductSelected(int index, Map selectedProduct) { if (selectedProduct.isEmpty) return; print('Selected Product: ${selectedProduct['product_name']}'); // Convert to double safely double sellingPrice = double.tryParse(selectedProduct['selling_price']?.toString() ?? '0.0') ?? 0.0; double gstRate = _gstType == 'with_gst' ? (double.tryParse(selectedProduct['gst']?.toString() ?? '0.0') ?? 0.0) : 0.0; int productId = int.tryParse(selectedProduct['id']?.toString() ?? '0') ?? 0; String barcode = selectedProduct['barcode']?.toString() ?? ''; setState(() { saleItems[index]['product_id'] = productId; saleItems[index]['price_incl_gst'] = sellingPrice; saleItems[index]['gst_percentage'] = gstRate; saleItems[index]['product_name'] = selectedProduct['product_name'] ?? 'Unknown'; saleItems[index]['barcode'] = barcode; _calculateProductItem(index); _calculateTotals(); }); // 🔥 AUTO FOCUS TO QUANTITY FIELD Future.delayed(Duration(milliseconds: 100), () { if (_quantityFocusNodes.length > index) { _quantityFocusNodes[index].requestFocus(); } }); } // 🔥 ENHANCED: Product search field with keyboard navigation Widget _buildProductSearchField(int index) { print('🔄 Building product search field for index $index'); print('📊 Total products available: ${widget.products.length}'); print('🔍 Filtered products count: ${widget.products.length}'); if (widget.products.isEmpty) { return Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.orange[50], border: Border.all(color: Colors.orange), borderRadius: BorderRadius.circular(4), ), child: Row( children: [ Icon(Icons.warning, color: Colors.orange, size: 16), SizedBox(width: 8), Expanded( child: Text( 'No products found. Please check if products exist for your account.', style: TextStyle(fontSize: 12, color: Colors.orange[800]), ), ), ], ), ); } return Row( children: [ Expanded( child: Autocomplete>( optionsBuilder: (TextEditingValue textEditingValue) { print('🔍 Searching for: ${textEditingValue.text}'); if (textEditingValue.text.isEmpty) { print('📝 Text field is empty, showing all products'); return widget.products.cast>(); } final filteredProducts = widget.products.where((product) { final productName = product['product_name']?.toString().toLowerCase() ?? ''; final searchText = textEditingValue.text.toLowerCase(); final bool matches = productName.contains(searchText); if (matches) { print('✅ Product match: $productName'); } return matches; }).cast>(); print('🎯 Found ${filteredProducts.length} matching products'); return filteredProducts; }, displayStringForOption: (Map product) { final name = product['product_name'] ?? 'Unknown'; print('📋 Displaying product: $name'); return name; }, onSelected: (Map product) { print('🎯 Product selected: ${product['product_name']}'); _onProductSelected(index, product); }, fieldViewBuilder: (BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) { // Update controller text based on current product if (saleItems[index]['product_name'] == null || saleItems[index]['product_name'].isEmpty) { textEditingController.clear(); } else { textEditingController.text = saleItems[index]['product_name']; } return TextField( controller: textEditingController, focusNode: _productFocusNodes[index], decoration: _getTableBottomLineDecoration().copyWith( hintText: "Select product (${widget.products.length} available)...", hintStyle: TextStyle(fontSize: 12, color: _textLight), prefixIcon: Icon(Icons.search, size: 18, color: _primaryColor), ), style: TextStyle(fontSize: 12, color: _textColor), onChanged: (value) { if (value.isEmpty) { setState(() { saleItems[index]['product_id'] = 0; saleItems[index]['product_name'] = ''; saleItems[index]['price_incl_gst'] = 0.0; saleItems[index]['gst_percentage'] = 0.0; saleItems[index]['barcode'] = ''; _calculateProductItem(index); _calculateTotals(); }); } }, onSubmitted: (value) { if (value.isNotEmpty) { var exactMatch = widget.products.firstWhere( (product) => (product['product_name']?.toString().toLowerCase() ?? '') == value.toLowerCase(), orElse: () => {}, ); if (exactMatch.isNotEmpty) { _onProductSelected(index, exactMatch); } } }, ); }, optionsViewBuilder: (BuildContext context, AutocompleteOnSelected> onSelected, Iterable> options) { print('📱 Options view shown with ${options.length} options'); return Align( alignment: Alignment.topLeft, child: Material( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: Container( constraints: const BoxConstraints(maxHeight: 200, maxWidth: 350), child: options.isEmpty ? Padding( padding: EdgeInsets.all(16), child: Text( 'No products found. Check if products exist in your database.', style: TextStyle(color: _textLight, fontSize: 12), ), ) : ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, itemCount: options.length, itemBuilder: (BuildContext context, int index) { final option = options.elementAt(index); final productName = option['product_name'] ?? 'Unknown'; final sellingPrice = double.tryParse(option['selling_price']?.toString() ?? '0.0') ?? 0.0; final gstRate = double.tryParse(option['gst']?.toString() ?? '0.0') ?? 0.0; final barcode = option['barcode']?.toString() ?? 'No Barcode'; return ListTile( title: Text( productName, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), ), subtitle: Text( 'Price: ₹${sellingPrice.toStringAsFixed(2)} | GST: ${gstRate.toStringAsFixed(1)}% | Barcode: $barcode', style: TextStyle(fontSize: 10, color: _textLight), ), dense: true, visualDensity: VisualDensity.compact, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), onTap: () => onSelected(option), ); }, ), ), ), ); }, ), ), SizedBox(width: 8), // Barcode Scanner Button // Container( // width: 44, // height: 44, // decoration: BoxDecoration( // color: _primaryColor, // borderRadius: BorderRadius.circular(8), // ), // child: IconButton( // icon: _isScanning // ? SizedBox( // width: 16, // height: 16, // child: CircularProgressIndicator( // strokeWidth: 2, // valueColor: AlwaysStoppedAnimation(Colors.white), // ), // ) // : Icon(Icons.qr_code_scanner, color: Colors.white, size: 20), // onPressed: () => _scanBarcode(index), // tooltip: 'Scan Barcode', // padding: EdgeInsets.zero, // ), // ), ], ); } // Helper method for text field decoration InputDecoration _getTableBottomLineDecoration() { return InputDecoration( border: UnderlineInputBorder(borderSide: BorderSide(color: _primaryColor.withOpacity(0.3))), enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: _primaryColor.withOpacity(0.3))), focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: _primaryColor)), contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 8), isDense: true, filled: true, fillColor: _cardColor, ); } // 🔥 ENHANCED: Quantity field that moves to NEXT PRODUCT when Enter is pressed Widget _buildLiteBlueNumberField({ required double value, required String label, required Function(double) onChanged, required int index, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(fontSize: 11, color: _textColor, fontWeight: FontWeight.w600)), SizedBox(height: 4), Container( height: 38, decoration: BoxDecoration( color: _cardColor, borderRadius: BorderRadius.circular(6), border: Border.all(color: _primaryColor.withOpacity(0.2)), ), child: TextField( focusNode: _quantityFocusNodes[index], keyboardType: TextInputType.numberWithOptions(decimal: true), onChanged: (text) { double newValue = double.tryParse(text) ?? 0.0; onChanged(newValue); }, onSubmitted: (value) { // 🔥 WHEN USER PRESSES ENTER IN QUANTITY FIELD: // 1. If there's a next product row, focus on its product search field // 2. If this is the last row, add a new product row and focus on it if (index < saleItems.length - 1) { // Move to next product's search field Future.delayed(Duration(milliseconds: 100), () { if (_productFocusNodes.length > index + 1) { _productFocusNodes[index + 1].requestFocus(); } }); } else { // This is the last row - add new product and focus on it _addProductItem(); Future.delayed(Duration(milliseconds: 100), () { if (_productFocusNodes.length > index + 1) { _productFocusNodes[index + 1].requestFocus(); } }); _showSnackBar('New product row added automatically'); } }, style: TextStyle(fontSize: 12, color: _textColor), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 10), filled: true, fillColor: _cardColor, ), controller: TextEditingController(text: value.toStringAsFixed(2)), ), ), ], ); } // Build GST Type Dropdown (only show if GST is enabled) Widget _buildGSTTypeDropdown(int index) { if (_gstType == 'without_gst') { return SizedBox.shrink(); // Hide GST dropdown if GST is disabled } return Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('GST %', style: TextStyle(fontSize: 11, color: _textColor, fontWeight: FontWeight.w600)), SizedBox(height: 4), Container( height: 38, decoration: BoxDecoration( color: _cardColor, borderRadius: BorderRadius.circular(6), border: Border.all(color: _primaryColor.withOpacity(0.2)), ), child: TextField( keyboardType: TextInputType.numberWithOptions(decimal: true), onChanged: (text) { double newValue = double.tryParse(text) ?? 0.0; _updateProductItem(index, 'gst_percentage', newValue); }, style: TextStyle(fontSize: 12, color: _textColor), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 10), filled: true, fillColor: _cardColor, ), controller: TextEditingController(text: saleItems[index]['gst_percentage'].toStringAsFixed(2)), ), ), ], ), ); } // Print Invoice with selected language Future _printInvoice(String invoiceNo, Map sale) async { try { if (_selectedLanguage == 'tamil') { await TamilPrintService.printTamilInvoice(invoiceNo, sale, widget.settings, widget.baseUrl); } else if (_selectedLanguage == 'hindi') { await HindiPrintService.printHindiInvoice(invoiceNo, sale, widget.settings, widget.baseUrl); } else if (_selectedLanguage == 'malayalam') { await MalayalamPrintService.printMalayalamInvoice(invoiceNo, sale, widget.settings, widget.baseUrl); } else { await EnglishPrintService.printEnglishInvoice(invoiceNo, sale, widget.settings, widget.baseUrl); } } catch (e) { print('Printing error: $e'); _showSnackBar('Printing error: $e'); } } void _autoResetForm() { setState(() { String timestamp = DateTime.now().millisecondsSinceEpoch.toString(); invoiceController.text = 'SALE-${timestamp.substring(timestamp.length - 6)}'; saleDateController.text = _getCurrentDate(); customerNameController.clear(); mobileNumberController.clear(); tenderAmountController.clear(); saleItems.clear(); _productFocusNodes.clear(); _quantityFocusNodes.clear(); _addProductItem(); // Reset all calculations totalTaxableValue = 0.0; totalGSTAmount = 0.0; totalCGSTAmount = 0.0; totalSGSTAmount = 0.0; totalDiscount = 0.0; grandTotal = 0.0; tenderAmount = 0.0; balanceAmount = 0.0; }); print('Form auto-reset completed. New invoice: ${invoiceController.text}'); } Future _saveAndPrint() async { if (_formKey.currentState!.validate()) { // Validate required fields if (customerNameController.text.isEmpty) { _showSnackBar('Please enter customer name'); return; } // Validate tender amount if (tenderAmountController.text.isEmpty || tenderAmount < grandTotal) { _showSnackBar('Please enter tender amount greater than or equal to grand total'); return; } // Validate products for (var i = 0; i < saleItems.length; i++) { var item = saleItems[i]; if (item['product_id'] == 0 || item['product_name']?.isEmpty == true) { _showSnackBar('Please select product for item ${i + 1}'); return; } if (item['quantity'] <= 0) { _showSnackBar('Please enter valid quantity for item ${i + 1}'); return; } } setState(() { _isSaving = true; }); // Prepare items data List> formattedItems = []; for (var i = 0; i < saleItems.length; i++) { var item = saleItems[i]; formattedItems.add({ 'product_id': item['product_id'], 'quantity': item['quantity'], 'price_incl_gst': item['price_incl_gst'], 'discount': item['discount'], 'gst_percentage': item['gst_percentage'], 'taxable_amount': item['taxable_amount'], 'total_amount': item['total_amount'], 'cgst_amount': item['cgst_amount'] ?? 0.0, 'sgst_amount': item['sgst_amount'] ?? 0.0, 'product_name': item['product_name'], 'barcode': item['barcode'] ?? '', }); } final Map requestBody = { 'action': 'add', 'invoice_number': invoiceController.text.trim(), 'customer_name': customerNameController.text.trim(), 'mobile_number': mobileNumberController.text.trim(), 'sale_date': saleDateController.text.trim(), 'total_discount': totalDiscount, 'total_taxable_value': totalTaxableValue, 'total_gst_amount': totalGSTAmount, 'total_cgst_amount': totalCGSTAmount, 'total_sgst_amount': totalSGSTAmount, 'grand_total': grandTotal, 'tender_amount': tenderAmount, 'balance_amount': balanceAmount, 'items': formattedItems, 'admin_id': widget.userData['id'] ?? 1, }; print('Sending sale with ${formattedItems.length} products'); try { final response = await http.post( Uri.parse(_salesUrl), headers: {'Content-Type': 'application/json'}, body: json.encode(requestBody), ).timeout(Duration(seconds: 30)); print('Response status: ${response.statusCode}'); // Handle JSON parsing safely Map data; try { data = json.decode(response.body); } catch (e) { print('JSON parsing error: $e'); _showSnackBar('Server response error. Please try again.'); setState(() { _isSaving = false; }); return; } if (response.statusCode == 200) { if (data['status'] == 'success') { // Generate sale data for PDF final saleData = { 'invoice_number': invoiceController.text, 'customer_name': customerNameController.text, 'mobile_number': mobileNumberController.text, 'sale_date': saleDateController.text, 'total_taxable_value': totalTaxableValue, 'total_gst_amount': totalGSTAmount, 'total_cgst_amount': totalCGSTAmount, 'total_sgst_amount': totalSGSTAmount, 'total_discount': totalDiscount, 'grand_total': grandTotal, 'tender_amount': tenderAmount, 'balance_amount': balanceAmount, 'items': formattedItems, 'business_profile': widget.businessProfile, 'settings': widget.settings, }; // Print using selected language await _printInvoice(invoiceController.text, saleData); _autoResetForm(); _showSnackBar('Sale completed successfully! Form reset for new sale.'); } else { _showSnackBar('Error: ${data['error'] ?? 'Failed to complete sale'}'); } } else { _showSnackBar('HTTP Error ${response.statusCode}: ${response.body}'); } } catch (e) { print('Error in _saveAndPrint: $e'); _showSnackBar('Network Error: $e'); } finally { setState(() { _isSaving = false; }); } } } void _clearForm() { _autoResetForm(); _showSnackBar('Form cleared successfully!'); } // Language Selection Dialog void _showLanguageSelectionDialog() { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Row( children: [ Icon(Icons.language, color: _primaryColor), SizedBox(width: 8), Text('Select Print Language'), ], ), content: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: Icon(Icons.language, color: _primaryColor), title: Text('English'), trailing: _selectedLanguage == 'english' ? Icon(Icons.check_circle, color: _primaryColor) : null, onTap: () { setState(() { _selectedLanguage = 'english'; }); Navigator.of(context).pop(); _showSnackBar('Print language set to English'); }, ), ListTile( leading: Icon(Icons.language, color: Colors.green), title: Text('Tamil'), trailing: _selectedLanguage == 'tamil' ? Icon(Icons.check_circle, color: Colors.green) : null, onTap: () { setState(() { _selectedLanguage = 'tamil'; }); Navigator.of(context).pop(); _showSnackBar('Print language set to Tamil'); }, ), ], ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('Cancel'), ), ], ); }, ); } // Open Settings Dialog void _openSettingsDialog() { showDialog( context: context, builder: (BuildContext context) { return SettingsDialog( currentSettings: widget.settings, userData: widget.userData, onSettingsUpdated: () { // Refresh settings in parent if (widget.onSettingsUpdated != null) { widget.onSettingsUpdated!(); } Navigator.of(context).pop(); _showSnackBar('Settings updated successfully!'); }, ); }, ); } void _showSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), duration: Duration(seconds: 3), backgroundColor: _primaryColor, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), ); } // Build Settings Preview Widget _buildSettingsPreview() { return GestureDetector( onTap: _openSettingsDialog, child: Container( margin: EdgeInsets.only(top: 4), padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _primaryLight, borderRadius: BorderRadius.circular(4), border: Border.all(color: _primaryColor.withOpacity(0.2)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.settings, size: 10, color: _primaryColor), SizedBox(width: 4), Text( 'GST: ${_gstType == 'with_gst' ? 'Yes' : 'No'} | Lang: ${_language.toUpperCase()} | Receipt: ${_receiptType.toUpperCase()} | Paper: ${_paperSize.toUpperCase()} | Logo: ${_printWithLogo ? 'Yes' : 'No'}', style: TextStyle(fontSize: 9, color: _primaryColor, fontWeight: FontWeight.w500), ), SizedBox(width: 4), Icon(Icons.edit, size: 10, color: _primaryColor), ], ), ), ); } // Loading button widget Widget _buildLoadingButton() { return Container( width: double.infinity, height: 55, decoration: BoxDecoration( color: _primaryColor.withOpacity(0.7), borderRadius: BorderRadius.circular(10), ), child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 22, height: 22, child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation(Colors.white), ), ), SizedBox(width: 12), Text( 'Saving & Printing...', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold), ), ], ), ), ); } @override Widget build(BuildContext context) { // Get business details for display String businessName = widget.businessProfile['business_name']?.toString() ?? 'Your Business'; String addressLine1 = widget.businessProfile['address_line1']?.toString() ?? ''; String addressLine2 = widget.businessProfile['address_line2']?.toString() ?? ''; String businessLogo = widget.businessProfile['business_logo']?.toString() ?? ''; String signature = widget.businessProfile['signature']?.toString() ?? ''; return Scaffold( backgroundColor: _backgroundColor, body: Form( key: _formKey, child: Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( children: [ // Business Header Card with Logo and Details Card( elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shadowColor: _primaryColor.withOpacity(0.1), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ // Business Logo if (businessLogo.isNotEmpty && _printWithLogo) Container( width: 50, height: 50, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: _primaryColor.withOpacity(0.3)), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( '${widget.baseUrl}$businessLogo', fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( color: _primaryLight, child: Icon(Icons.business, color: _primaryColor), ); }, ), ), ) else if (_printWithLogo) Container( width: 50, height: 50, decoration: BoxDecoration( color: _primaryLight, borderRadius: BorderRadius.circular(8), border: Border.all(color: _primaryColor.withOpacity(0.3)), ), child: Icon(Icons.business, color: _primaryColor), ), SizedBox(width: 12), // Business Details Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( businessName, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: _textColor, ), ), if (addressLine1.isNotEmpty) Text( addressLine1, style: TextStyle(fontSize: 12, color: _textLight), ), if (addressLine2.isNotEmpty) Text( addressLine2, style: TextStyle(fontSize: 12, color: _textLight), ), // Display current settings with edit button _buildSettingsPreview(), ], ), ), // Invoice Number Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _primaryLight, borderRadius: BorderRadius.circular(20), border: Border.all(color: _primaryColor.withOpacity(0.3)), ), child: Text( invoiceController.text, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: _primaryDark, ), ), ), ], ), ), ), SizedBox(height: 16), // Customer Details Card Card( elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shadowColor: _primaryColor.withOpacity(0.1), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Row( children: [ Icon(Icons.person_outline, color: _primaryColor, size: 20), SizedBox(width: 8), Text('Customer Details', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _textColor)), ], ), SizedBox(height: 16), Row( children: [ Expanded( child: _buildLiteBlueFormField( controller: customerNameController, label: 'Customer Name ', icon: Icons.person, ), ), SizedBox(width: 12), Expanded( child: _buildLiteBlueFormField( controller: mobileNumberController, label: 'Mobile Number', keyboardType: TextInputType.phone, icon: Icons.phone, ), ), ], ), SizedBox(height: 12), Row( children: [ Expanded( child: _buildLiteBlueFormField( controller: invoiceController, label: 'Invoice Number ', isRequired: true, icon: Icons.receipt, ), ), SizedBox(width: 12), Expanded( child: _buildLiteBlueFormField( controller: saleDateController, label: 'Sale Date *', isRequired: true, icon: Icons.calendar_today, ), ), ], ), ], ), ), ), SizedBox(height: 16), // Products Card Card( elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shadowColor: _primaryColor.withOpacity(0.1), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Row( children: [ Icon(Icons.shopping_basket_outlined, color: _primaryColor, size: 20), SizedBox(width: 8), Text('Products', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _textColor)), Spacer(), SizedBox(width: 8), // Language Selection Button Container( margin: EdgeInsets.only(right: 8), child: ElevatedButton.icon( onPressed: _showLanguageSelectionDialog, icon: Icon(Icons.language, size: 16), label: Text( _selectedLanguage == 'english' ? 'EN' : 'TA', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( backgroundColor: _selectedLanguage == 'english' ? _primaryColor : Colors.green, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), elevation: 2, ), ), ), ElevatedButton.icon( onPressed: _addProductItem, icon: Icon(Icons.add, size: 18), label: Text('Add Product', style: TextStyle(fontSize: 13)), style: ElevatedButton.styleFrom( backgroundColor: _primaryColor, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), elevation: 2, ), ), ], ), SizedBox(height: 16), // Product List with Enhanced Search and Barcode Scanning ...saleItems.asMap().entries.map((entry) { int index = entry.key; Map item = entry.value; return Container( margin: EdgeInsets.only(bottom: 12), padding: EdgeInsets.all(12), decoration: BoxDecoration( color: _primaryLight, borderRadius: BorderRadius.circular(8), border: Border.all(color: _primaryColor.withOpacity(0.1)), ), child: Column( children: [ Row( children: [ Icon(Icons.inventory_2_outlined, size: 16, color: _primaryColor), SizedBox(width: 6), Text('Product ${index + 1}', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: _textColor)), Spacer(), if (saleItems.length > 1) IconButton( icon: Icon(Icons.remove_circle_outlined, size: 18), onPressed: () => _removeProductItem(index), color: Colors.red, padding: EdgeInsets.zero, ), ], ), SizedBox(height: 8), // Enhanced Product Search Field with Barcode Scanner _buildProductSearchField(index), SizedBox(height: 8), // Display barcode if available if (item['barcode'] != null && item['barcode'].toString().isNotEmpty) Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _backgroundColor, borderRadius: BorderRadius.circular(4), border: Border.all(color: _primaryColor.withOpacity(0.2)), ), child: Row( children: [ Icon(Icons.qr_code, size: 12, color: _primaryColor), SizedBox(width: 4), Text( 'Barcode: ${item['barcode']}', style: TextStyle(fontSize: 10, color: _textLight), ), ], ), ), SizedBox(height: 8), // Quantity, Price, Discount, GST (conditional) Row( children: [ Expanded( child: _buildLiteBlueNumberField( value: item['quantity'].toDouble(), label: 'Qty', onChanged: (value) => _updateProductItem(index, 'quantity', value.toInt()), index: index, ), ), SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Price', style: TextStyle(fontSize: 11, color: _textColor, fontWeight: FontWeight.w600)), SizedBox(height: 4), Container( height: 38, decoration: BoxDecoration( color: _cardColor, borderRadius: BorderRadius.circular(6), border: Border.all(color: _primaryColor.withOpacity(0.2)), ), child: TextField( keyboardType: TextInputType.numberWithOptions(decimal: true), onChanged: (text) { double newValue = double.tryParse(text) ?? 0.0; _updateProductItem(index, 'price_incl_gst', newValue); }, style: TextStyle(fontSize: 12, color: _textColor), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 10), filled: true, fillColor: _cardColor, ), controller: TextEditingController(text: item['price_incl_gst'].toStringAsFixed(2)), ), ), ], ), ), SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Disc %', style: TextStyle(fontSize: 11, color: _textColor, fontWeight: FontWeight.w600)), SizedBox(height: 4), Container( height: 38, decoration: BoxDecoration( color: _cardColor, borderRadius: BorderRadius.circular(6), border: Border.all(color: _primaryColor.withOpacity(0.2)), ), child: TextField( keyboardType: TextInputType.numberWithOptions(decimal: true), onChanged: (text) { double newValue = double.tryParse(text) ?? 0.0; _updateProductItem(index, 'discount', newValue); }, style: TextStyle(fontSize: 12, color: _textColor), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 10), filled: true, fillColor: _cardColor, ), controller: TextEditingController(text: item['discount'].toStringAsFixed(2)), ), ), ], ), ), SizedBox(width: 8), _buildGSTTypeDropdown(index), // Conditional GST field ], ), SizedBox(height: 8), // Amounts Row( children: [ Expanded( child: Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _backgroundColor, borderRadius: BorderRadius.circular(4), ), child: Text( 'Taxable: ₹${item['taxable_amount'].toStringAsFixed(2)}', style: TextStyle(fontSize: 11, color: _textColor, fontWeight: FontWeight.w500), ), ), ), SizedBox(width: 8), Expanded( child: Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _primaryColor.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Text( 'Total: ₹${item['total_amount'].toStringAsFixed(2)}', style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold, color: _primaryColor), ), ), ), ], ), ], ), ); }).toList(), ], ), ), ), SizedBox(height: 16), // Summary Card Card( elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shadowColor: _primaryColor.withOpacity(0.1), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Row( children: [ Icon(Icons.calculate_outlined, color: _primaryColor, size: 20), SizedBox(width: 8), Text('Summary', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _textColor)), ], ), SizedBox(height: 12), _buildLiteBlueSummaryRow('Total Taxable Value:', '₹${totalTaxableValue.toStringAsFixed(2)}'), if (_gstType == 'with_gst') ...[ // Only show GST details if GST is enabled _buildLiteBlueSummaryRow('Total GST Amount:', '₹${totalGSTAmount.toStringAsFixed(2)}'), _buildLiteBlueSummaryRow( 'CGST Amount:', '₹${totalCGSTAmount.toStringAsFixed(2)}' ), _buildLiteBlueSummaryRow( 'SGST Amount:', '₹${totalCGSTAmount.toStringAsFixed(2)}' ), ], _buildLiteBlueSummaryRow('Total Discount:', '₹${totalDiscount.toStringAsFixed(2)}'), Divider(height: 12, color: _primaryLight), _buildLiteBlueSummaryRow( 'GRAND TOTAL:', '₹${grandTotal.toStringAsFixed(2)}', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _primaryColor) ), ], ), ), ), SizedBox(height: 16), // Tender & Balance Card Card( elevation: 3, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shadowColor: _primaryColor.withOpacity(0.1), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Row( children: [ Icon(Icons.payment_outlined, color: _primaryColor, size: 20), SizedBox(width: 8), Text('Payment Details', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: _textColor)), ], ), SizedBox(height: 16), Row( children: [ Expanded( child: _buildLiteBlueFormField( controller: tenderAmountController, label: 'Tender Amount *', isRequired: true, icon: Icons.currency_rupee, keyboardType: TextInputType.numberWithOptions(decimal: true), ), ), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Balance Amount', style: TextStyle(fontWeight: FontWeight.w600, color: _textColor, fontSize: 13), ), SizedBox(height: 6), Container( height: 44, decoration: BoxDecoration( color: balanceAmount >= 0 ? Color(0xFFE8F5E8) : Color(0xFFFFEBEE), borderRadius: BorderRadius.circular(8), border: Border.all( color: balanceAmount >= 0 ? Colors.green : Colors.red, width: 1, ), ), child: Center( child: Text( '₹${balanceAmount.toStringAsFixed(2)}', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: balanceAmount >= 0 ? Colors.green : Colors.red, ), ), ), ), ], ), ), ], ), SizedBox(height: 12), Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: _primaryLight, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Icon( balanceAmount >= 0 ? Icons.check_circle : Icons.warning, color: balanceAmount >= 0 ? Colors.green : Colors.orange, size: 18, ), SizedBox(width: 8), Expanded( child: Text( balanceAmount >= 0 ? 'Payment completed. Balance to return: ₹${balanceAmount.toStringAsFixed(2)}' : 'Insufficient tender amount. Short by: ₹${balanceAmount.abs().toStringAsFixed(2)}', style: TextStyle( color: balanceAmount >= 0 ? Colors.green : Colors.orange, fontSize: 12, fontWeight: FontWeight.w500, ), ), ), ], ), ), ], ), ), ), SizedBox(height: 20), // Action Buttons Row( children: [ Expanded( child: _isSaving ? _buildLoadingButton() : ElevatedButton( onPressed: _saveAndPrint, style: ElevatedButton.styleFrom( backgroundColor: _primaryColor, foregroundColor: Colors.white, minimumSize: Size(double.infinity, 55), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), elevation: 3, shadowColor: _primaryColor.withOpacity(0.3), padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.save_alt, size: 20), SizedBox(width: 10), Text('Save & Print Invoice ($_selectedLanguage)', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), SizedBox(width: 10), Icon(Icons.print, size: 20), ], ), ), ), ], ), SizedBox(height: 20), ], ), ), ), ), ); } Widget _buildLiteBlueFormField({ required TextEditingController controller, required String label, required IconData icon, bool isRequired = false, TextInputType keyboardType = TextInputType.text, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( label, style: TextStyle(fontWeight: FontWeight.w600, color: _textColor, fontSize: 13), ), if (isRequired) Text(' *', style: TextStyle(color: Colors.red, fontSize: 13)), ], ), SizedBox(height: 6), Container( height: 44, decoration: BoxDecoration( color: _cardColor, borderRadius: BorderRadius.circular(8), border: Border.all(color: _primaryColor.withOpacity(0.2)), ), child: TextField( controller: controller, keyboardType: keyboardType, style: TextStyle(fontSize: 14, color: _textColor), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 12), prefixIcon: Icon(icon, color: _primaryColor, size: 18), filled: true, fillColor: _cardColor, ), ), ), ], ); } Widget _buildLiteBlueSummaryRow(String label, String value, {TextStyle? style}) { return Padding( padding: EdgeInsets.symmetric(vertical: 6), child: Row( children: [ Text(label, style: TextStyle(fontWeight: FontWeight.w600, fontSize: 14, color: _textColor)), Spacer(), Text(value, style: style ?? TextStyle(fontWeight: FontWeight.w600, fontSize: 14, color: _textColor)), ], ), ); } @override void dispose() { tenderAmountController.removeListener(_calculateBalance); tenderAmountController.dispose(); // Dispose all focus nodes for (var node in _productFocusNodes) { node.dispose(); } for (var node in _quantityFocusNodes) { node.dispose(); } super.dispose(); } } // Settings Dialog for Quick Settings Update class SettingsDialog extends StatefulWidget { final Map currentSettings; final Map userData; final VoidCallback onSettingsUpdated; const SettingsDialog({ Key? key, required this.currentSettings, required this.userData, required this.onSettingsUpdated, }) : super(key: key); @override _SettingsDialogState createState() => _SettingsDialogState(); } class _SettingsDialogState extends State { late String _selectedReceiptType; late String _selectedPaperSize; late bool _printWithLogo; late bool _printWithSignature; bool _isSaving = false; @override void initState() { super.initState(); _selectedReceiptType = widget.currentSettings['receipt_type']?.toString().toLowerCase() ?? 'thermal'; _selectedPaperSize = widget.currentSettings['paper_size']?.toString().toLowerCase() ?? '80mm'; _printWithLogo = (widget.currentSettings['print_with_logo']?.toString() ?? '1') == '1'; _printWithSignature = (widget.currentSettings['print_with_signature']?.toString() ?? '1') == '1'; } Future _saveSettings() async { setState(() { _isSaving = true; }); try { final response = await http.post( Uri.parse('http://localhost/shop_billing/Admin_register/settings.php'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'action': 'save_settings', 'admin_id': widget.userData['id']?.toString() ?? '1', 'receipt_type': _selectedReceiptType, 'paper_size': _selectedPaperSize, 'print_with_logo': _printWithLogo ? '1' : '0', 'print_with_signature': _printWithSignature ? '1' : '0', }), ); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); if (responseData['status'] == 'success') { widget.onSettingsUpdated(); } } } catch (e) { print('Error saving settings: $e'); } finally { setState(() { _isSaving = false; }); } } @override Widget build(BuildContext context) { return AlertDialog( title: Row( children: [ Icon(Icons.settings, color: Color(0xFF3B82F6)), SizedBox(width: 8), Text('Receipt Settings'), ], ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Receipt Type', style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 8), DropdownButtonFormField( value: _selectedReceiptType, items: [ DropdownMenuItem(value: 'thermal', child: Text('Thermal Receipt')), DropdownMenuItem(value: 'a4', child: Text('A4 Paper Receipt')), ], onChanged: (value) { setState(() { _selectedReceiptType = value!; }); }, decoration: InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), SizedBox(height: 16), Text('Paper Size', style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 8), DropdownButtonFormField( value: _selectedPaperSize, items: [ DropdownMenuItem(value: '80mm', child: Text('80mm Thermal Paper')), DropdownMenuItem(value: '58mm', child: Text('58mm Thermal Paper')), DropdownMenuItem(value: 'a4', child: Text('A4 Paper')), ], onChanged: (value) { setState(() { _selectedPaperSize = value!; }); }, decoration: InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), SizedBox(height: 16), Row( children: [ Checkbox( value: _printWithLogo, onChanged: (value) { setState(() { _printWithLogo = value!; // 🔥 AUTO-DISABLE SIGNATURE WHEN LOGO IS DISABLED if (!_printWithLogo) { _printWithSignature = false; } }); }, ), SizedBox(width: 8), Text('Print with Logo', style: TextStyle(fontWeight: FontWeight.bold)), ], ), SizedBox(height: 8), Row( children: [ Checkbox( value: _printWithSignature, onChanged: _printWithLogo ? (value) { // 🔥 ONLY ENABLE IF LOGO IS ON setState(() { _printWithSignature = value!; }); } : null, ), SizedBox(width: 8), Text( 'Print with Signature', style: TextStyle( fontWeight: FontWeight.bold, color: _printWithLogo ? Colors.black : Colors.grey, // 🔥 GREY OUT WHEN DISABLED ), ), ], ), if (!_printWithLogo) // 🔥 SHOW INFO MESSAGE WHEN DISABLED Padding( padding: EdgeInsets.only(top: 8, left: 8), child: Text( 'Signature is only available when Logo is enabled', style: TextStyle(fontSize: 12, color: Colors.orange, fontStyle: FontStyle.italic), ), ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('Cancel'), ), ElevatedButton( onPressed: _isSaving ? null : _saveSettings, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF3B82F6), ), child: _isSaving ? SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Text('Save'), ), ], ); } }