diff --git a/lib/presentation/pages/transaction/transaction_page.dart b/lib/presentation/pages/transaction/transaction_page.dart index a5e10e3..7216c3c 100644 --- a/lib/presentation/pages/transaction/transaction_page.dart +++ b/lib/presentation/pages/transaction/transaction_page.dart @@ -1,39 +1,15 @@ +import 'dart:math' as math; + import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:line_icons/line_icons.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, - }); -} +import '../../components/button/button.dart'; +import '../../components/spacer/spacer.dart'; +import 'widgets/appbar.dart'; +import 'widgets/status_tile.dart'; +import 'widgets/transaction_tile.dart'; @RoutePage() class TransactionPage extends StatefulWidget { @@ -43,665 +19,264 @@ class TransactionPage extends StatefulWidget { State createState() => _TransactionPageState(); } -class _TransactionPageState extends State { - String selectedFilter = 'All'; - DateTime selectedDate = DateTime.now(); +class _TransactionPageState extends State + with TickerProviderStateMixin { + late AnimationController _fadeController; + late AnimationController _slideController; + late AnimationController _rotationController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + late Animation _rotationAnimation; - final List transactions = [ + // Filter state + String selectedFilter = 'All'; + final List filterOptions = [ + 'All', + 'Completed', + 'Pending', + 'Refunded', + ]; + + @override + void initState() { + super.initState(); + + _fadeController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + + _slideController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _rotationController = AnimationController( + duration: const Duration(seconds: 3), + vsync: this, + )..repeat(); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _slideAnimation = + Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( + CurvedAnimation(parent: _slideController, curve: Curves.elasticOut), + ); + + _rotationAnimation = Tween( + begin: 0, + end: 2 * math.pi, + ).animate(_rotationController); + + _fadeController.forward(); + _slideController.forward(); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _rotationController.dispose(); + super.dispose(); + } + + final sampleTransactions = [ Transaction( - id: 'TRX001', - customerName: 'Ahmad Rizki', - date: DateTime.now().subtract(Duration(hours: 2)), - total: 125000, - status: 'Completed', + id: 'TXN001', + customerName: 'John Doe', + date: DateTime.now().subtract(const Duration(hours: 2)), + totalAmount: 125000, + itemCount: 3, 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), - ], + status: TransactionStatus.completed, + receiptNumber: 'RCP-2024-001', ), Transaction( - id: 'TRX002', - customerName: 'Siti Nurhaliza', - date: DateTime.now().subtract(Duration(hours: 4)), - total: 85000, - status: 'Completed', + id: 'TXN002', + customerName: 'Jane Smith', + date: DateTime.now().subtract(const Duration(hours: 5)), + totalAmount: 87500, + itemCount: 2, 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), - ], + status: TransactionStatus.pending, + receiptNumber: 'RCP-2024-002', ), 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), - ], + id: 'TXN003', + customerName: 'Bob Johnson', + date: DateTime.now().subtract(const Duration(days: 1)), + totalAmount: 250000, + itemCount: 5, + paymentMethod: 'Credit Card', + status: TransactionStatus.refunded, + receiptNumber: 'RCP-2024-003', ), ]; + // Filter transactions based on selected status + List get filteredTransactions { + if (selectedFilter == 'All') { + return sampleTransactions; + } + + TransactionStatus? filterStatus; + switch (selectedFilter) { + case 'Completed': + filterStatus = TransactionStatus.completed; + break; + case 'Pending': + filterStatus = TransactionStatus.pending; + break; + case 'Refunded': + filterStatus = TransactionStatus.refunded; + break; + } + + return sampleTransactions + .where((transaction) => transaction.status == filterStatus) + .toList(); + } + + // Build filter chip + @override Widget build(BuildContext context) { 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%', + body: CustomScrollView( + slivers: [ + // Custom App Bar with Hero Effect + SliverAppBar( + expandedHeight: 120, + floating: true, + pinned: true, + backgroundColor: AppColor.primary, + centerTitle: false, + flexibleSpace: TransactionAppBar( + rotationAnimation: _rotationAnimation, ), - ), - 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, - ), - ), - ), + actions: [ + ActionIconButton(onTap: () {}, icon: LineIcons.filter), + SpaceWidth(8), ], ), - 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, + // Pinned Filter Section + SliverPersistentHeader( + pinned: true, + delegate: _FilterHeaderDelegate( + child: Container( + color: AppColor.background, + padding: EdgeInsets.fromLTRB( + AppValue.padding, + 10, + AppValue.padding, + 10, ), - ), - 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, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, 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, - ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: filterOptions.map((option) { + final index = filterOptions.indexOf(option); + return Padding( + padding: EdgeInsets.only( + right: index < filterOptions.length - 1 ? 8 : 0, + ), + child: TransactionStatusTile( + label: option, + isSelected: option == selectedFilter, + onSelected: (isSelected) { + if (isSelected) { + setState(() { + selectedFilter = option; + }); + } + }, + ), + ); + }).toList(), ), ), ], ), - ], - ), - 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: () {}, - ); - } + // Content + SliverPadding( + padding: EdgeInsets.all(AppValue.padding), + sliver: SliverList( + delegate: SliverChildListDelegate([ + FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: Column( + children: [ + // Show filtered transaction count + if (selectedFilter != 'All') + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Row( + children: [ + Text( + '${filteredTransactions.length} ${selectedFilter.toLowerCase()} transaction${filteredTransactions.length != 1 ? 's' : ''}', + style: TextStyle( + color: AppColor.textSecondary, + fontSize: 14, + ), + ), + ], + ), + ), - 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( + // Transaction List + filteredTransactions.isEmpty + ? Container( + padding: const EdgeInsets.symmetric( + vertical: 40, + ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - item.name, - style: TextStyle( - color: AppColor.textPrimary, - fontWeight: FontWeight.w600, + Icon( + LineIcons.receipt, + size: 64, + color: AppColor.textSecondary.withOpacity( + 0.5, ), ), + const SizedBox(height: 16), Text( - 'Qty: ${item.quantity}', + 'No ${selectedFilter.toLowerCase()} transactions found', style: TextStyle( color: AppColor.textSecondary, - fontSize: 12, + fontSize: 16, ), ), ], ), + ) + : Column( + children: filteredTransactions.map(( + transaction, + ) { + return TransactionTile( + transaction: transaction, + onTap: () {}, + ); + }).toList(), ), - 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, + ), + ]), ), ), ], @@ -709,3 +284,30 @@ class _TransactionPageState extends State { ); } } + +// Custom delegate for pinned filter header +class _FilterHeaderDelegate extends SliverPersistentHeaderDelegate { + final Widget child; + + _FilterHeaderDelegate({required this.child}); + + @override + double get minExtent => 70; // Minimum height when collapsed + + @override + double get maxExtent => 70; // Maximum height when expanded + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return child; + } + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { + return false; + } +} diff --git a/lib/presentation/pages/transaction/widgets/appbar.dart b/lib/presentation/pages/transaction/widgets/appbar.dart new file mode 100644 index 0000000..d868958 --- /dev/null +++ b/lib/presentation/pages/transaction/widgets/appbar.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +class TransactionAppBar extends StatelessWidget { + final Animation rotationAnimation; + const TransactionAppBar({super.key, required this.rotationAnimation}); + + @override + Widget build(BuildContext context) { + return FlexibleSpaceBar( + titlePadding: const EdgeInsets.only(left: 20, bottom: 16), + title: Text( + 'Transaksi', + style: AppStyle.xl.copyWith( + color: AppColor.textWhite, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + + background: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: AppColor.primaryGradient, + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Stack( + children: [ + Positioned( + right: -20, + top: -20, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: rotationAnimation.value, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.1), + ), + ), + ); + }, + ), + ), + Positioned( + left: -30, + bottom: -30, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: -rotationAnimation.value * 0.5, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColor.white.withOpacity(0.05), + ), + ), + ); + }, + ), + ), + Positioned( + right: 80, + bottom: 30, + child: AnimatedBuilder( + animation: rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: -rotationAnimation.value * 0.2, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColor.white.withOpacity(0.08), + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/pages/transaction/widgets/status_tile.dart b/lib/presentation/pages/transaction/widgets/status_tile.dart new file mode 100644 index 0000000..11757fe --- /dev/null +++ b/lib/presentation/pages/transaction/widgets/status_tile.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +import '../../../../common/theme/theme.dart'; + +class TransactionStatusTile extends StatelessWidget { + final String label; + final bool isSelected; + final void Function(bool)? onSelected; + const TransactionStatusTile({ + super.key, + required this.label, + this.isSelected = false, + this.onSelected, + }); + + @override + Widget build(BuildContext context) { + return FilterChip( + label: Text( + label, + style: TextStyle( + color: isSelected ? Colors.white : AppColor.primary, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, + ), + ), + selected: isSelected, + onSelected: onSelected, + backgroundColor: Colors.white, + selectedColor: AppColor.primary, + checkmarkColor: Colors.white, + side: BorderSide( + color: isSelected ? AppColor.primary : Colors.grey.shade300, + width: 1, + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + ); + } +} diff --git a/lib/presentation/pages/transaction/widgets/transaction_tile.dart b/lib/presentation/pages/transaction/widgets/transaction_tile.dart new file mode 100644 index 0000000..2e8923e --- /dev/null +++ b/lib/presentation/pages/transaction/widgets/transaction_tile.dart @@ -0,0 +1,401 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../../../../common/theme/theme.dart'; + +// Model untuk Transaction +class Transaction { + final String id; + final String customerName; + final DateTime date; + final double totalAmount; + final int itemCount; + final String paymentMethod; + final TransactionStatus status; + final String? receiptNumber; + + Transaction({ + required this.id, + required this.customerName, + required this.date, + required this.totalAmount, + required this.itemCount, + required this.paymentMethod, + required this.status, + this.receiptNumber, + }); +} + +enum TransactionStatus { completed, pending, cancelled, refunded } + +class TransactionTile extends StatelessWidget { + final Transaction transaction; + final VoidCallback? onTap; + final VoidCallback? onPrint; + final VoidCallback? onRefund; + + const TransactionTile({ + super.key, + required this.transaction, + this.onTap, + this.onPrint, + this.onRefund, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + child: Card( + elevation: 4, + shadowColor: AppColor.primaryWithOpacity(0.1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: AppColor.border, width: 0.5), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(16), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [AppColor.backgroundLight, AppColor.background], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Row + _buildHeaderRow(), + const SizedBox(height: 12), + + // Transaction Info + _buildTransactionInfo(), + const SizedBox(height: 16), + + // Amount Section + _buildAmountSection(), + const SizedBox(height: 16), + + // Footer with Actions + _buildFooterActions(), + ], + ), + ), + ), + ), + ); + } + + Widget _buildHeaderRow() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + transaction.receiptNumber ?? 'TXN-${transaction.id}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColor.textPrimary, + ), + ), + const SizedBox(height: 2), + Text( + DateFormat('dd MMM yyyy, HH:mm').format(transaction.date), + style: const TextStyle( + fontSize: 12, + color: AppColor.textSecondary, + ), + ), + ], + ), + _buildStatusChip(), + ], + ); + } + + Widget _buildStatusChip() { + Color statusColor; + String statusText; + IconData statusIcon; + + switch (transaction.status) { + case TransactionStatus.completed: + statusColor = AppColor.success; + statusText = 'Completed'; + statusIcon = Icons.check_circle; + break; + case TransactionStatus.pending: + statusColor = AppColor.warning; + statusText = 'Pending'; + statusIcon = Icons.schedule; + break; + case TransactionStatus.cancelled: + statusColor = AppColor.error; + statusText = 'Cancelled'; + statusIcon = Icons.cancel; + break; + case TransactionStatus.refunded: + statusColor = AppColor.info; + statusText = 'Refunded'; + statusIcon = Icons.undo; + break; + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: statusColor.withOpacity(0.3), width: 1), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(statusIcon, size: 14, color: statusColor), + const SizedBox(width: 4), + Text( + statusText, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: statusColor, + ), + ), + ], + ), + ); + } + + Widget _buildTransactionInfo() { + return Row( + children: [ + Expanded( + flex: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.person_outline, size: 16, color: AppColor.primary), + const SizedBox(width: 6), + Expanded( + child: Text( + transaction.customerName, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: AppColor.textPrimary, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.shopping_bag_outlined, + size: 16, + color: AppColor.primary, + ), + const SizedBox(width: 6), + Text( + '${transaction.itemCount} items', + style: const TextStyle( + fontSize: 13, + color: AppColor.textSecondary, + ), + ), + ], + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: AppColor.primaryWithOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _getPaymentIcon(transaction.paymentMethod), + size: 16, + color: AppColor.primary, + ), + const SizedBox(width: 6), + Text( + transaction.paymentMethod, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: AppColor.primary, + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildAmountSection() { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: AppColor.primaryGradient, + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: AppColor.primary.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Total Amount', + style: TextStyle( + fontSize: 14, + color: AppColor.textWhite, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + 'Rp ${NumberFormat('#,###').format(transaction.totalAmount)}', + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: AppColor.textWhite, + ), + ), + ], + ), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.backgroundLight.withOpacity(0.2), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.attach_money, + color: AppColor.textWhite, + size: 24, + ), + ), + ], + ), + ); + } + + Widget _buildFooterActions() { + return Row( + children: [ + Expanded( + child: Text( + 'ID: ${transaction.id}', + style: const TextStyle( + fontSize: 11, + color: AppColor.textLight, + fontWeight: FontWeight.w500, + ), + ), + ), + if (transaction.status == TransactionStatus.completed) ...[ + _buildActionButton( + icon: Icons.print, + label: 'Print', + onPressed: onPrint, + color: AppColor.info, + ), + const SizedBox(width: 8), + _buildActionButton( + icon: Icons.undo, + label: 'Refund', + onPressed: onRefund, + color: AppColor.warning, + ), + ], + ], + ); + } + + Widget _buildActionButton({ + required IconData icon, + required String label, + required VoidCallback? onPressed, + required Color color, + }) { + return Material( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + child: InkWell( + onTap: onPressed, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 16, color: color), + const SizedBox(width: 4), + Text( + label, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: color, + ), + ), + ], + ), + ), + ), + ); + } + + IconData _getPaymentIcon(String paymentMethod) { + switch (paymentMethod.toLowerCase()) { + case 'cash': + return Icons.payments; + case 'card': + case 'credit card': + case 'debit card': + return Icons.credit_card; + case 'qris': + case 'qr code': + return Icons.qr_code; + case 'transfer': + case 'bank transfer': + return Icons.account_balance; + case 'e-wallet': + case 'digital wallet': + return Icons.account_balance_wallet; + default: + return Icons.payment; + } + } +}