diff --git a/lib/common/theme/theme.dart b/lib/common/theme/theme.dart index fff9fac..9c4561a 100644 --- a/lib/common/theme/theme.dart +++ b/lib/common/theme/theme.dart @@ -43,7 +43,7 @@ class ThemeApp { ), ), bottomNavigationBarTheme: BottomNavigationBarThemeData( - backgroundColor: AppColor.background, + backgroundColor: AppColor.white, selectedItemColor: AppColor.primary, unselectedItemColor: AppColor.textSecondary, selectedLabelStyle: AppStyle.md.copyWith( diff --git a/lib/presentation/pages/transaction/transaction_page.dart b/lib/presentation/pages/transaction/transaction_page.dart index dd46d95..a5e10e3 100644 --- a/lib/presentation/pages/transaction/transaction_page.dart +++ b/lib/presentation/pages/transaction/transaction_page.dart @@ -1,12 +1,711 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import '../../../common/theme/theme.dart'; + +class Transaction { + final String id; + final String customerName; + final DateTime date; + final double total; + final String status; + final List items; + final String paymentMethod; + + Transaction({ + required this.id, + required this.customerName, + required this.date, + required this.total, + required this.status, + required this.items, + required this.paymentMethod, + }); +} + +class TransactionItem { + final String name; + final int quantity; + final double price; + + TransactionItem({ + required this.name, + required this.quantity, + required this.price, + }); +} + @RoutePage() -class TransactionPage extends StatelessWidget { +class TransactionPage extends StatefulWidget { const TransactionPage({super.key}); + @override + State createState() => _TransactionPageState(); +} + +class _TransactionPageState extends State { + String selectedFilter = 'All'; + DateTime selectedDate = DateTime.now(); + + final List transactions = [ + Transaction( + id: 'TRX001', + customerName: 'Ahmad Rizki', + date: DateTime.now().subtract(Duration(hours: 2)), + total: 125000, + status: 'Completed', + paymentMethod: 'Cash', + items: [ + TransactionItem(name: 'Nasi Goreng', quantity: 2, price: 25000), + TransactionItem(name: 'Es Teh', quantity: 3, price: 5000), + TransactionItem(name: 'Ayam Bakar', quantity: 1, price: 35000), + ], + ), + Transaction( + id: 'TRX002', + customerName: 'Siti Nurhaliza', + date: DateTime.now().subtract(Duration(hours: 4)), + total: 85000, + status: 'Completed', + paymentMethod: 'QRIS', + items: [ + TransactionItem(name: 'Gado-gado', quantity: 1, price: 20000), + TransactionItem(name: 'Jus Jeruk', quantity: 2, price: 12000), + TransactionItem(name: 'Kerupuk', quantity: 1, price: 5000), + ], + ), + Transaction( + id: 'TRX003', + customerName: 'Budi Santoso', + date: DateTime.now().subtract(Duration(hours: 6)), + total: 200000, + status: 'Pending', + paymentMethod: 'Debit Card', + items: [ + TransactionItem(name: 'Paket Keluarga', quantity: 1, price: 150000), + TransactionItem(name: 'Es Campur', quantity: 2, price: 15000), + ], + ), + ]; + @override Widget build(BuildContext context) { - return Center(child: Text('TransactionPage')); + return Scaffold( + backgroundColor: AppColor.background, + appBar: AppBar( + elevation: 0, + backgroundColor: AppColor.white, + title: Text( + 'Transactions', + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + actions: [ + IconButton( + icon: Icon(Icons.search, color: AppColor.textPrimary), + onPressed: () {}, + ), + IconButton( + icon: Icon(Icons.filter_list, color: AppColor.textPrimary), + onPressed: () => _showFilterBottomSheet(context), + ), + ], + ), + body: Column( + children: [ + _buildSummaryCards(), + _buildFilterTabs(), + Expanded(child: _buildTransactionList()), + ], + ), + floatingActionButton: FloatingActionButton.extended( + onPressed: () {}, + backgroundColor: AppColor.primary, + icon: Icon(Icons.add, color: AppColor.white), + label: Text( + 'New Sale', + style: TextStyle(color: AppColor.white, fontWeight: FontWeight.w600), + ), + ), + ); + } + + Widget _buildSummaryCards() { + return Container( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: _buildSummaryCard( + 'Today\'s Sales', + 'Rp 2,450,000', + Icons.trending_up, + AppColor.success, + '+12%', + ), + ), + SizedBox(width: 12), + Expanded( + child: _buildSummaryCard( + 'Total Orders', + '48', + Icons.receipt_long, + AppColor.info, + '+8%', + ), + ), + ], + ), + ); + } + + Widget _buildSummaryCard( + String title, + String value, + IconData icon, + Color color, + String change, + ) { + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 10, + offset: Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon(icon, color: color, size: 24), + Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: AppColor.success.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + change, + style: TextStyle( + color: AppColor.success, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + SizedBox(height: 12), + Text( + value, + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 4), + Text( + title, + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } + + Widget _buildFilterTabs() { + final filters = ['All', 'Completed', 'Pending', 'Cancelled']; + + return Container( + height: 60, + padding: EdgeInsets.symmetric(horizontal: 16), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: filters.length, + itemBuilder: (context, index) { + final filter = filters[index]; + final isSelected = selectedFilter == filter; + + return GestureDetector( + onTap: () { + setState(() { + selectedFilter = filter; + }); + }, + child: Container( + margin: EdgeInsets.only(right: 12, top: 8, bottom: 8), + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? AppColor.primary : AppColor.white, + borderRadius: BorderRadius.circular(25), + border: Border.all( + color: isSelected ? AppColor.primary : AppColor.border, + width: 1, + ), + ), + child: Text( + filter, + style: TextStyle( + color: isSelected ? AppColor.white : AppColor.textSecondary, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ), + ); + }, + ), + ); + } + + Widget _buildTransactionList() { + final filteredTransactions = transactions.where((transaction) { + if (selectedFilter == 'All') return true; + return transaction.status == selectedFilter; + }).toList(); + + return ListView.builder( + padding: EdgeInsets.symmetric(horizontal: 16), + itemCount: filteredTransactions.length, + itemBuilder: (context, index) { + final transaction = filteredTransactions[index]; + return _buildTransactionCard(transaction); + }, + ); + } + + Widget _buildTransactionCard(Transaction transaction) { + return GestureDetector( + onTap: () => _showTransactionDetail(transaction), + child: Container( + margin: EdgeInsets.only(bottom: 12), + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppColor.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.black.withOpacity(0.05), + blurRadius: 10, + offset: Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + transaction.id, + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 4), + Text( + transaction.customerName, + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 14, + ), + ), + SizedBox(height: 4), + Text( + '${_formatTime(transaction.date)} • ${transaction.paymentMethod}', + style: TextStyle( + color: AppColor.textLight, + fontSize: 12, + ), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'Rp ${_formatCurrency(transaction.total)}', + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 8), + Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: _getStatusColor( + transaction.status, + ).withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + transaction.status, + style: TextStyle( + color: _getStatusColor(transaction.status), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ], + ), + SizedBox(height: 12), + Row( + children: [ + Icon( + Icons.shopping_bag_outlined, + color: AppColor.textLight, + size: 16, + ), + SizedBox(width: 8), + Text( + '${transaction.items.length} items', + style: TextStyle(color: AppColor.textLight, fontSize: 12), + ), + Spacer(), + Icon( + Icons.arrow_forward_ios, + color: AppColor.textLight, + size: 14, + ), + ], + ), + ], + ), + ), + ); + } + + Color _getStatusColor(String status) { + switch (status) { + case 'Completed': + return AppColor.success; + case 'Pending': + return AppColor.warning; + case 'Cancelled': + return AppColor.error; + default: + return AppColor.textSecondary; + } + } + + String _formatCurrency(double amount) { + return amount + .toStringAsFixed(0) + .replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]},', + ); + } + + String _formatTime(DateTime dateTime) { + final now = DateTime.now(); + final difference = now.difference(dateTime); + + if (difference.inHours < 1) { + return '${difference.inMinutes}m ago'; + } else if (difference.inHours < 24) { + return '${difference.inHours}h ago'; + } else { + return '${difference.inDays}d ago'; + } + } + + void _showFilterBottomSheet(BuildContext context) { + showModalBottomSheet( + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) { + return Container( + padding: EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Filter Transactions', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 20), + _buildFilterOption('Date Range', Icons.date_range), + _buildFilterOption('Payment Method', Icons.payment), + _buildFilterOption('Amount Range', Icons.attach_money), + _buildFilterOption('Customer', Icons.person), + SizedBox(height: 20), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => Navigator.pop(context), + style: OutlinedButton.styleFrom( + side: BorderSide(color: AppColor.border), + padding: EdgeInsets.symmetric(vertical: 12), + ), + child: Text( + 'Reset', + style: TextStyle(color: AppColor.textSecondary), + ), + ), + ), + SizedBox(width: 12), + Expanded( + child: ElevatedButton( + onPressed: () => Navigator.pop(context), + style: ElevatedButton.styleFrom( + backgroundColor: AppColor.primary, + padding: EdgeInsets.symmetric(vertical: 12), + ), + child: Text( + 'Apply', + style: TextStyle(color: AppColor.white), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + } + + Widget _buildFilterOption(String title, IconData icon) { + return ListTile( + contentPadding: EdgeInsets.zero, + leading: Icon(icon, color: AppColor.textSecondary), + title: Text(title, style: TextStyle(color: AppColor.textPrimary)), + trailing: Icon( + Icons.arrow_forward_ios, + size: 16, + color: AppColor.textLight, + ), + onTap: () {}, + ); + } + + void _showTransactionDetail(Transaction transaction) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) { + return DraggableScrollableSheet( + initialChildSize: 0.7, + maxChildSize: 0.9, + minChildSize: 0.5, + builder: (context, scrollController) { + return Container( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: AppColor.borderLight, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Transaction Detail', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: _getStatusColor( + transaction.status, + ).withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + transaction.status, + style: TextStyle( + color: _getStatusColor(transaction.status), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + SizedBox(height: 20), + _buildDetailRow('Transaction ID', transaction.id), + _buildDetailRow('Customer', transaction.customerName), + _buildDetailRow( + 'Date', + '${transaction.date.day}/${transaction.date.month}/${transaction.date.year}', + ), + _buildDetailRow('Payment Method', transaction.paymentMethod), + SizedBox(height: 20), + Text( + 'Items Ordered', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + SizedBox(height: 12), + Expanded( + child: ListView.builder( + controller: scrollController, + itemCount: transaction.items.length, + itemBuilder: (context, index) { + final item = transaction.items[index]; + return Container( + margin: EdgeInsets.only(bottom: 8), + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.backgroundLight, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.name, + style: TextStyle( + color: AppColor.textPrimary, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Qty: ${item.quantity}', + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 12, + ), + ), + ], + ), + ), + Text( + 'Rp ${_formatCurrency(item.price * item.quantity)}', + style: TextStyle( + color: AppColor.textPrimary, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + }, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: AppColor.border)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Amount', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + Text( + 'Rp ${_formatCurrency(transaction.total)}', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + ], + ), + ), + ], + ), + ); + }, + ); + }, + ); + } + + Widget _buildDetailRow(String label, String value) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle(color: AppColor.textSecondary, fontSize: 14), + ), + Text( + value, + style: TextStyle( + color: AppColor.textPrimary, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ); } }