OnboardingPozoAppv1/flutter_app/lib/screens/billing_dashboard_screen.dart

1044 lines
43 KiB
Dart
Raw Permalink Normal View History

import 'dart:math';
import 'package:flutter/material.dart';
import '../route_transitions.dart';
import '../feedback_helper.dart';
class ProductItem {
final String id;
final String name;
final double price;
final int stock;
final String code;
ProductItem({
required this.id,
required this.name,
required this.price,
required this.stock,
required this.code,
});
}
class BillingItem {
final ProductItem product;
int quantity;
BillingItem({
required this.product,
required this.quantity,
});
}
class BillingDashboardScreen extends StatefulWidget {
final String businessName;
final String businessType;
final String gstin;
final bool barcodeScanner;
final bool kotManagement;
final bool inventoryAlerts;
const BillingDashboardScreen({
super.key,
required this.businessName,
required this.businessType,
required this.gstin,
required this.barcodeScanner,
required this.kotManagement,
required this.inventoryAlerts,
});
@override
State<BillingDashboardScreen> createState() => _BillingDashboardScreenState();
}
class _BillingDashboardScreenState extends State<BillingDashboardScreen> {
late List<ProductItem> _products;
final List<BillingItem> _billingList = [];
bool _printingInvoice = false;
String _invoiceNumber = 'POZO-26402';
String _scanMessage = '';
final Map<String, List<ProductItem>> _inventoryPresets = {
'supermarket': [
ProductItem(id: 'sm1', name: 'Premium Basmati Rice (1kg)', price: 120.0, stock: 45, code: '89012300052'),
ProductItem(id: 'sm2', name: 'Fresh Farm Milk (1L)', price: 65.0, stock: 2, code: '89045300010'),
ProductItem(id: 'sm3', name: 'Refined Cooking Oil (1L)', price: 165.0, stock: 18, code: '89010320491'),
ProductItem(id: 'sm4', name: 'Refined White Sugar (1kg)', price: 48.0, stock: 3, code: '89034200421'),
ProductItem(id: 'sm5', name: 'Organic Green T-Tea (250g)', price: 110.0, stock: 24, code: '89512300011'),
ProductItem(id: 'sm6', name: 'Whole Wheat Atta (5kg)', price: 230.0, stock: 7, code: '89011210042'),
],
'restaurant': [
ProductItem(id: 'rt1', name: 'Fresh Paneer Butter Masala', price: 240.0, stock: 19, code: '9901230'),
ProductItem(id: 'rt2', name: 'Butter Tandoori Naan', price: 45.0, stock: 15, code: '9904530'),
ProductItem(id: 'rt3', name: 'Dum Veg Biryani Deluxe', price: 190.0, stock: 2, code: '9901032'),
ProductItem(id: 'rt4', name: 'Crispy Butter Masala Dosa', price: 90.0, stock: 22, code: '9903420'),
ProductItem(id: 'rt5', name: 'Rich Chocolate Milkshake', price: 120.0, stock: 3, code: '9951230'),
ProductItem(id: 'rt6', name: 'Hot Roasted Filter Coffee', price: 50.0, stock: 30, code: '9901121'),
],
'clothing': [
ProductItem(id: 'cl1', name: 'Premium Oxford Cotton Shirt', price: 899.0, stock: 8, code: '77012301'),
ProductItem(id: 'cl2', name: 'Slim Fit Dark Jeans', price: 1599.0, stock: 2, code: '77045302'),
ProductItem(id: 'cl3', name: 'Designer Summer Slip Dress', price: 1299.0, stock: 5, code: '77010323'),
ProductItem(id: 'cl4', name: 'Graphic Printed Tee-Shirt', price: 450.0, stock: 40, code: '77034204'),
ProductItem(id: 'cl5', name: 'Breathable Sports Sneakers', price: 1899.0, stock: 3, code: '77512305'),
ProductItem(id: 'cl6', name: 'Stitch Leather Belt Brown', price: 349.0, stock: 12, code: '77011216'),
],
'kirana': [
ProductItem(id: 'kr1', name: 'Premium Toor Dal (1kg)', price: 145.0, stock: 15, code: '4401230'),
ProductItem(id: 'kr2', name: 'Iodized Powdered Salt (1kg)', price: 22.0, stock: 50, code: '4404530'),
ProductItem(id: 'kr3', name: 'Herbal Moisturizer Soap', price: 42.0, stock: 2, code: '4401032'),
ProductItem(id: 'kr4', name: 'Anti-Germ Toothpaste (200g)', price: 85.0, stock: 16, code: '4403420'),
ProductItem(id: 'kr5', name: 'Dishwash Cleaning Soap', price: 25.0, stock: 3, code: '4451230'),
ProductItem(id: 'kr6', name: 'Red Chili Powder (200g)', price: 65.0, stock: 18, code: '4401121'),
],
'others': [
ProductItem(id: 'ot1', name: 'Utility Household Scrubber', price: 35.0, stock: 33, code: '5501230'),
ProductItem(id: 'ot2', name: 'AAA Rechargeable Batteries', price: 120.0, stock: 4, code: '5504530'),
ProductItem(id: 'ot3', name: 'LED High Bright Focus Bulb', price: 140.0, stock: 2, code: '5501032'),
ProductItem(id: 'ot4', name: 'Water Bottle Stainless (1L)', price: 320.0, stock: 11, code: '5503420'),
ProductItem(id: 'ot5', name: 'Microfiber Floor Duster', price: 75.0, stock: 24, code: '5551230'),
ProductItem(id: 'ot6', name: 'Multi-plug Protection Adapter', price: 240.0, stock: 9, code: '5501121'),
]
};
@override
void initState() {
super.initState();
_products = _inventoryPresets[widget.businessType] ?? _inventoryPresets['supermarket']!;
}
void _handleAddItem(ProductItem product) {
setState(() {
final index = _billingList.indexWhere((element) => element.product.id == product.id);
if (index != -1) {
final existingItem = _billingList[index];
if (existingItem.quantity >= product.stock && widget.inventoryAlerts) {
_showErrorSnackBar('Cannot add more. Inventory limit reached for ${product.name}!');
return;
}
existingItem.quantity += 1;
} else {
_billingList.add(BillingItem(product: product, quantity: 1));
}
});
}
void _handleRemoveOne(String productId) {
setState(() {
final index = _billingList.indexWhere((element) => element.product.id == productId);
if (index != -1) {
if (_billingList[index].quantity > 1) {
_billingList[index].quantity -= 1;
} else {
_billingList.removeAt(index);
}
}
});
}
void _simulateBarcodeScan() {
AppFeedback.click();
if (!widget.barcodeScanner) return;
final random = Random();
final randomProduct = _products[random.nextInt(_products.length)];
_handleAddItem(randomProduct);
setState(() {
_scanMessage = '🎯 Scanned EAN Barcode [${randomProduct.code}] - Added ${randomProduct.name}!';
});
Future.delayed(const Duration(milliseconds: 3000), () {
if (mounted) {
setState(() {
_scanMessage = '';
});
}
});
}
void _clearRegister() {
setState(() {
_billingList.clear();
});
}
void _showErrorSnackBar(String message) {
showTopAlert(context, message, isError: true);
}
double get _subtotal {
return _billingList.fold(0.0, (sum, item) => sum + (item.product.price * item.quantity));
}
bool get _hasGstin {
return widget.gstin.trim().isNotEmpty;
}
double get _cgst => _hasGstin ? _subtotal * 0.09 : 0.0;
double get _sgst => _hasGstin ? _subtotal * 0.09 : 0.0;
double get _total => _subtotal + _cgst + _sgst;
void _handleReviewAndGenerate() {
AppFeedback.success();
final random = Random();
final invoiceVal = 'POZO-${random.nextInt(90000) + 10000}';
setState(() {
_invoiceNumber = invoiceVal;
_printingInvoice = true;
});
}
void _closeReceiptAndReset() {
AppFeedback.click();
setState(() {
_printingInvoice = false;
_billingList.clear();
});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDesktop = MediaQuery.of(context).size.width >= 600;
return Stack(
children: [
Scaffold(
backgroundColor: const Color(0xFF1E293B), // Slate-900 like
appBar: AppBar(
backgroundColor: const Color(0xFF0F172A), // Slate-950 like
elevation: 4,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () {
AppFeedback.click();
Navigator.pop(context);
},
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.businessName.isNotEmpty ? widget.businessName : 'Sharma General Store',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
Row(
children: [
Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Color(0xFF10B981),
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
const Text(
'Live Register',
style: TextStyle(
color: Colors.grey,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
],
),
],
),
actions: [
if (widget.barcodeScanner)
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Directionality(
textDirection: TextDirection.rtl,
child: InteractiveScale(
child: ElevatedButton.icon(
onPressed: _simulateBarcodeScan,
style: ElevatedButton.styleFrom(
minimumSize: const Size(0, 32),
backgroundColor: theme.colorScheme.primary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
icon: const Icon(Icons.qr_code_scanner, size: 16),
label: const Text(
'Scan Item',
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
),
),
),
),
),
if (widget.kotManagement)
Container(
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 12),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.cyan.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.cyan.withOpacity(0.3)),
),
child: const Center(
child: Text(
'KOT',
style: TextStyle(
color: Colors.cyanAccent,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 8),
],
),
body: SafeArea(
child: Column(
children: [
// Scan Banner Message
if (_scanMessage.isNotEmpty)
Container(
width: double.infinity,
color: const Color(0xFF10B981),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.stars, color: Colors.white, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
_scanMessage,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
],
),
),
// Main Layout Split
Expanded(
child: isDesktop
? Row(
children: [
Expanded(child: _buildCatalogPart(theme)),
_buildLedgerTrayPart(theme, isDesktop),
],
)
: Column(
children: [
Expanded(child: _buildCatalogPart(theme)),
const Divider(color: Color(0xFF334155), height: 1),
SizedBox(
height: 240,
child: _buildLedgerTrayPart(theme, isDesktop),
),
],
),
),
// Bottom Checkout sum action for Mobile ONLY
if (!isDesktop) _buildBottomComputationBar(theme),
],
),
),
),
// Custom Overlay for Printable Invoice
if (_printingInvoice) _buildThermalReceiptOverlay(theme),
],
);
}
Widget _buildCatalogPart(ThemeData theme) {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'STORE CATALOG',
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 11,
letterSpacing: 1.0,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFF334155),
borderRadius: BorderRadius.circular(10),
),
child: Text(
widget.businessType.toUpperCase(),
style: const TextStyle(
color: Colors.white70,
fontSize: 9,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 10),
Expanded(
child: GridView.builder(
physics: const BouncingScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 1.4,
),
itemCount: _products.length,
itemBuilder: (context, index) {
final prod = _products[index];
final billingIndex = _billingList.indexWhere((element) => element.product.id == prod.id);
final countInCart = billingIndex != -1 ? _billingList[billingIndex].quantity : 0;
final remainingStock = prod.stock - countInCart;
final isLowStock = widget.inventoryAlerts && remainingStock <= 3;
final isOutOfStock = widget.inventoryAlerts && remainingStock <= 0;
return SlideFadeEntrance(
delay: Duration(milliseconds: index * 40),
duration: const Duration(milliseconds: 300),
child: InteractiveScale(
tapScale: isOutOfStock ? 1.0 : 0.95,
hoverScale: isOutOfStock ? 1.0 : 1.03,
child: InkWell(
onTap: isOutOfStock ? null : () => _handleAddItem(prod),
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: countInCart > 0
? const Color(0xFF1E1B4B) // Deep indigo accent
: const Color(0xFF1E293B).withOpacity(0.8),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: countInCart > 0 ? theme.colorScheme.primary : const Color(0xFF334155),
width: countInCart > 0 ? 1.5 : 1.0,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
prod.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 11,
),
),
const SizedBox(height: 4),
Text(
'${prod.price.toStringAsFixed(0)}',
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w800,
fontSize: 13,
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.inventoryAlerts)
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: isLowStock
? Colors.red.withOpacity(0.15)
: const Color(0xFF334155),
borderRadius: BorderRadius.circular(6),
),
child: Text(
isOutOfStock ? 'Out of stock' : '$remainingStock Stock',
style: TextStyle(
color: isLowStock ? Colors.redAccent : Colors.grey,
fontSize: 8,
fontWeight: FontWeight.bold,
),
),
)
else
const SizedBox(),
if (countInCart > 0)
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: theme.colorScheme.primary,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
'$countInCart',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 10,
),
),
)
],
)
],
),
),
),
),
);
},
),
)
],
),
);
}
Widget _buildLedgerTrayPart(ThemeData theme, bool isDesktop) {
return Container(
width: isDesktop ? 300 : double.infinity,
color: const Color(0xFF0F172A).withOpacity(0.3),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'BILLING TRAY (${_billingList.length})',
style: const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 11,
letterSpacing: 1.0,
),
),
if (_billingList.isNotEmpty)
GestureDetector(
onTap: _clearRegister,
child: const Row(
children: [
Icon(Icons.refresh, color: Colors.grey, size: 12),
SizedBox(width: 4),
Text(
'Clear',
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 10,
),
)
],
),
)
],
),
const SizedBox(height: 10),
Expanded(
child: _billingList.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.shopping_cart_outlined, color: Colors.grey[600], size: 30),
const SizedBox(height: 8),
const Text(
'No products selected.',
style: TextStyle(color: Colors.grey, fontSize: 11),
),
const Text(
'Tap items in catalog above.',
style: TextStyle(color: Colors.white30, fontSize: 10),
),
],
),
)
: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: _billingList.length,
itemBuilder: (context, index) {
final item = _billingList[index];
return Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0xFF1E293B),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFF334155), width: 0.5),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.product.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 11,
),
),
const SizedBox(height: 2),
Text(
'${item.product.price.toStringAsFixed(0)} × ${item.quantity}',
style: const TextStyle(
color: Colors.grey,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
],
),
),
Row(
children: [
GestureDetector(
onTap: () => _handleRemoveOne(item.product.id),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: const Color(0xFF334155),
borderRadius: BorderRadius.circular(6),
),
child: const Icon(Icons.remove, size: 14, color: Colors.white),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
'${item.quantity}',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
GestureDetector(
onTap: () => _handleAddItem(item.product),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: const Color(0xFF334155),
borderRadius: BorderRadius.circular(6),
),
child: const Icon(Icons.add, size: 14, color: Colors.white),
),
),
],
),
],
),
);
},
),
),
if (isDesktop) ...[
const Divider(color: Color(0xFF334155), height: 16),
_buildComputationCalculations(theme),
const SizedBox(height: 10),
InteractiveScale(
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
disabledBackgroundColor: const Color(0xFF1E293B),
minimumSize: const Size.fromHeight(48),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.receipt, size: 16),
SizedBox(width: 8),
Text('Generate Invoice'),
],
),
),
)
]
],
),
);
}
Widget _buildComputationCalculations(ThemeData theme) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Subtotal', style: TextStyle(color: Colors.grey, fontSize: 11)),
Text('${_subtotal.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold)),
],
),
if (_hasGstin) ...[
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('CGST (9%)', style: TextStyle(color: Colors.grey, fontSize: 11)),
Text('${_cgst.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
],
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('SGST (9%)', style: TextStyle(color: Colors.grey, fontSize: 11)),
Text('${_sgst.toStringAsFixed(2)}', style: const TextStyle(color: Colors.white70, fontSize: 11)),
],
),
],
const Padding(
padding: EdgeInsets.symmetric(vertical: 6.0),
child: Divider(color: Color(0xFF334155), height: 1),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Grand Total', style: TextStyle(color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold)),
Text(
'${_total.toStringAsFixed(2)}',
style: TextStyle(
color: theme.colorScheme.primary,
fontSize: 14,
fontWeight: FontWeight.w900,
),
),
],
),
],
);
}
Widget _buildBottomComputationBar(ThemeData theme) {
return Container(
color: const Color(0xFF0F172A),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('GRAND TOTAL', style: TextStyle(color: Colors.grey, fontSize: 9, fontWeight: FontWeight.bold)),
Text(
'${_total.toStringAsFixed(2)}',
style: TextStyle(
color: theme.colorScheme.primary,
fontSize: 18,
fontWeight: FontWeight.w900,
),
)
],
),
InteractiveScale(
hoverScale: _billingList.isNotEmpty ? 1.03 : 1.0,
tapScale: _billingList.isNotEmpty ? 0.96 : 1.0,
child: ElevatedButton(
onPressed: _billingList.isEmpty ? null : _handleReviewAndGenerate,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
minimumSize: const Size(180, 44),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.print, size: 16),
SizedBox(width: 8),
Text('Review & Invoice', style: TextStyle(fontSize: 12)),
],
),
),
)
],
)
],
),
);
}
Widget _buildThermalReceiptOverlay(ThemeData theme) {
return Scaffold(
backgroundColor: Colors.black87,
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30),
child: Container(
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 340),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Thermal dotted-dash receipt border box
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[400]!, width: 2, style: BorderStyle.solid),
borderRadius: BorderRadius.circular(4),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Column(
children: [
Text(
widget.businessName.isNotEmpty ? widget.businessName.toUpperCase() : 'SHARMA GENERAL STORE',
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.w900,
fontSize: 16,
fontFamily: 'monospace',
),
),
const SizedBox(height: 2),
Text(
'${widget.businessType.toUpperCase()} COUNTER',
style: const TextStyle(
color: Colors.grey,
fontSize: 9,
fontFamily: 'monospace',
),
),
if (_hasGstin) ...[
const SizedBox(height: 4),
Text(
'GSTIN: ${widget.gstin}',
style: const TextStyle(
color: Colors.black,
fontSize: 8,
fontWeight: FontWeight.bold,
fontFamily: 'monospace',
),
),
]
],
),
),
const SizedBox(height: 12),
const Divider(color: Colors.black38, thickness: 1),
const SizedBox(height: 4),
// Metadata Info
_buildReceiptRow('Invoice No', _invoiceNumber),
_buildReceiptRow('Date Track', '${DateTime.now().day}/${DateTime.now().month}/${DateTime.now().year}'),
_buildReceiptRow('Terminal', 'Pozo Counter'),
const SizedBox(height: 4),
const Divider(color: Colors.black38, thickness: 1),
const SizedBox(height: 4),
// Headers
const Row(
children: [
Expanded(
flex: 6,
child: Text('Item Description', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black, fontSize: 8, fontFamily: 'monospace')),
),
Expanded(
flex: 2,
child: Text('Qty', textAlign: TextAlign.right, style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black, fontSize: 8, fontFamily: 'monospace')),
),
Expanded(
flex: 4,
child: Text('Price', textAlign: TextAlign.right, style: TextStyle(fontWeight: FontWeight.bold, color: Colors.black, fontSize: 8, fontFamily: 'monospace')),
),
],
),
const SizedBox(height: 6),
// Products
..._billingList.map((item) => Padding(
padding: const EdgeInsets.only(bottom: 6.0),
child: Row(
children: [
Expanded(
flex: 6,
child: Text(
item.product.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.black87, fontSize: 9, fontFamily: 'monospace'),
),
),
Expanded(
flex: 2,
child: Text(
'x${item.quantity}',
textAlign: TextAlign.right,
style: const TextStyle(color: Colors.black87, fontSize: 9, fontFamily: 'monospace'),
),
),
Expanded(
flex: 4,
child: Text(
'${(item.product.price * item.quantity).toStringAsFixed(0)}',
textAlign: TextAlign.right,
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 9, fontFamily: 'monospace'),
),
),
],
),
)),
const Divider(color: Colors.black38, thickness: 1),
const SizedBox(height: 4),
// Ledger math sum
_buildReceiptRow('Cart Total:', '${_subtotal.toStringAsFixed(2)}'),
if (_hasGstin) ...[
_buildReceiptRow('CGST (9%):', '${_cgst.toStringAsFixed(2)}'),
_buildReceiptRow('SGST (9%):', '${_sgst.toStringAsFixed(2)}'),
],
const SizedBox(height: 4),
_buildReceiptRow('NET TOTAL:', '${_total.toStringAsFixed(2)}', isBold: true),
const SizedBox(height: 8),
const Divider(color: Colors.black38, thickness: 1),
const SizedBox(height: 8),
const Center(
child: Column(
children: [
Text(
'THANK YOU FOR YOUR PATRONAGE!',
style: TextStyle(color: Colors.black54, fontSize: 8, fontWeight: FontWeight.bold, fontFamily: 'monospace'),
),
SizedBox(height: 4),
Text(
'Powered by PozoApp',
style: TextStyle(color: Colors.red, fontSize: 8, fontWeight: FontWeight.bold, fontFamily: 'monospace'),
),
],
),
),
],
),
),
const SizedBox(height: 16),
InteractiveScale(
child: ElevatedButton.icon(
onPressed: () {
AppFeedback.click();
showTopAlert(
context,
'Thermal Printed: $_invoiceNumber successfully!',
icon: Icons.print,
);
},
icon: const Icon(Icons.print, size: 16),
label: const Text('Simulate Print Receipt', style: TextStyle(fontSize: 12)),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(42),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
),
const SizedBox(height: 8),
InteractiveScale(
child: ElevatedButton(
onPressed: _closeReceiptAndReset,
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
minimumSize: const Size.fromHeight(42),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Text('New Ledger Sale', style: TextStyle(fontSize: 12)),
),
),
],
),
),
),
),
);
}
Widget _buildReceiptRow(String label, String val, {bool isBold = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 9,
fontFamily: 'monospace',
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: isBold ? Colors.black : Colors.black87,
),
),
Text(
val,
style: TextStyle(
fontSize: 9,
fontFamily: 'monospace',
fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
color: isBold ? Colors.black : Colors.black87,
),
),
],
),
);
}
}