import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/data/models/response/order_response_model.dart'; import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart'; import 'package:enaklo_pos/presentation/refund/dialog/refund_error_dialog.dart'; import 'package:enaklo_pos/presentation/refund/dialog/refund_success_dialog.dart'; import 'package:enaklo_pos/presentation/refund/widgets/refund_appbar.dart'; import 'package:enaklo_pos/presentation/refund/widgets/refund_info_tile.dart'; import 'package:enaklo_pos/presentation/refund/widgets/refund_order_Item_tile.dart'; import 'package:enaklo_pos/presentation/refund/widgets/refund_reason_tile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class RefundPage extends StatefulWidget { final Order selectedOrder; const RefundPage({super.key, required this.selectedOrder}); @override State createState() => _RefundPageState(); } class _RefundPageState extends State with TickerProviderStateMixin { final TextEditingController _reasonController = TextEditingController(); final TextEditingController _refundAmountController = TextEditingController(); final ScrollController _leftPanelScrollController = ScrollController(); final ScrollController _rightPanelScrollController = ScrollController(); final ScrollController _itemsScrollController = ScrollController(); String selectedReason = 'Barang Rusak'; late AnimationController _slideController; late AnimationController _fadeController; late AnimationController _scaleController; late Animation _slideAnimation; late Animation _fadeAnimation; late Animation _scaleAnimation; final List> refundReasons = [ { 'value': 'Barang Rusak', 'icon': Icons.broken_image, 'color': AppColors.primary }, {'value': 'Salah Item', 'icon': Icons.swap_horiz, 'color': Colors.orange}, { 'value': 'Tidak Sesuai Pesanan', 'icon': Icons.error_outline, 'color': Colors.amber }, { 'value': 'Permintaan Customer', 'icon': Icons.person, 'color': Colors.blue }, { 'value': 'Kualitas Tidak Baik', 'icon': Icons.thumb_down, 'color': Colors.purple }, { 'value': 'Lainnya', 'icon': Icons.more_horiz, 'color': Colors.red, }, ]; @override void initState() { super.initState(); _initializeAnimations(); _refundAmountController.text = (widget.selectedOrder.totalAmount ?? 0).toString(); } void _initializeAnimations() { _slideController = AnimationController( duration: Duration(milliseconds: 1200), vsync: this, ); _fadeController = AnimationController( duration: Duration(milliseconds: 800), vsync: this, ); _scaleController = AnimationController( duration: Duration(milliseconds: 600), vsync: this, ); _slideAnimation = Tween( begin: Offset(0.0, 1.0), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: Curves.elasticOut, )); _fadeAnimation = Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _fadeController, curve: Curves.easeInOut, )); _scaleAnimation = Tween( begin: 0.8, end: 1.0, ).animate(CurvedAnimation( parent: _scaleController, curve: Curves.elasticOut, )); _fadeController.forward(); _slideController.forward(); _scaleController.forward(); } @override void dispose() { _slideController.dispose(); _fadeController.dispose(); _scaleController.dispose(); _reasonController.dispose(); _refundAmountController.dispose(); _leftPanelScrollController.dispose(); _rightPanelScrollController.dispose(); _itemsScrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { state.when( initial: () {}, loading: () {}, success: () { _showSuccessDialog(); }, error: (message) { _showErrorDialog(message); }, ); }, child: Scaffold( backgroundColor: Color(0xFFF5F7FA), body: FadeTransition( opacity: _fadeAnimation, child: Column( children: [ RefundAppbar( order: widget.selectedOrder, ), Expanded( child: Padding( padding: EdgeInsets.all(24.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Left Panel - Order Summary (Scrollable) Expanded( flex: 3, child: Scrollbar( controller: _leftPanelScrollController, thumbVisibility: true, child: SingleChildScrollView( controller: _leftPanelScrollController, child: _buildOrderSummaryPanel(), ), ), ), SizedBox(width: 24), // Right Panel - Refund Configuration (Scrollable) Expanded( flex: 4, child: Scrollbar( controller: _rightPanelScrollController, thumbVisibility: true, child: SingleChildScrollView( controller: _rightPanelScrollController, child: _buildRefundConfigPanel(context), ), ), ), ], ), ), ), ], ), ), ), ); } Widget _buildOrderSummaryPanel() { return ScaleTransition( scale: _scaleAnimation, child: Column( children: [ // Order Info Card Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), ), child: Padding( padding: EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( gradient: LinearGradient( colors: [ const Color.fromARGB(255, 96, 56, 148), AppColors.primary ], ), borderRadius: BorderRadius.circular(16), ), child: Icon( Icons.receipt_long, color: Colors.white, size: 24, ), ), SizedBox(width: 20), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Detail Pesanan', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), SpaceHeight(4), Text( (widget.selectedOrder.createdAt ?? DateTime.now()) .toFormattedDate3(), style: TextStyle( color: Colors.grey[600], fontSize: 14, ), ), ], ), ), ], ), SpaceHeight(20), // Order Details Grid Row( children: [ Expanded( child: RefundInfoTile( title: 'Meja', value: widget.selectedOrder.tableNumber ?? 'Takeaway', icon: Icons.table_restaurant, color: Colors.blue, ), ), SizedBox(width: 16), Expanded( child: RefundInfoTile( title: 'Tipe', value: widget.selectedOrder.orderType ?? 'N/A', icon: Icons.shopping_bag_outlined, color: Colors.purple, ), ), ], ), SpaceHeight(16), Row( children: [ Expanded( child: RefundInfoTile( title: 'Status', value: widget.selectedOrder.status?.toUpperCase() ?? 'N/A', icon: Icons.check_circle_outline, color: _getStatusColor(widget.selectedOrder.status), ), ), SizedBox(width: 16), Expanded( child: RefundInfoTile( title: 'Items', value: '${widget.selectedOrder.orderItems?.length ?? 0}', icon: Icons.inventory_2_outlined, color: Colors.orange, ), ), ], ), ], ), ), ), SpaceHeight(24), // Payment Summary Card Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), ), child: Padding( padding: EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( gradient: LinearGradient( colors: [ const Color.fromARGB(255, 96, 56, 148), AppColors.primary ], ), borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.account_balance_wallet_outlined, color: AppColors.white, size: 24, ), ), SizedBox(width: 16), Text( 'Ringkasan Pembayaran', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), SpaceHeight(24), _buildPaymentRow( 'Subtotal', widget.selectedOrder.subtotal ?? 0), _buildPaymentRow( 'Pajak', widget.selectedOrder.taxAmount ?? 0), _buildPaymentRow( 'Diskon', -(widget.selectedOrder.discountAmount ?? 0)), Padding( padding: EdgeInsets.symmetric(vertical: 16), child: Divider(thickness: 2, color: Colors.grey[200]), ), _buildPaymentRow( 'Total Dibayar', widget.selectedOrder.totalAmount ?? 0, isTotal: true, ), ], ), ), ), SpaceHeight(24), // Extra space for scroll ], ), ); } Widget _buildRefundConfigPanel(BuildContext context) { return SlideTransition( position: _slideAnimation, child: Column( children: [ // Refund Reason Card Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), ), child: Padding( padding: EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( gradient: LinearGradient( colors: [ const Color.fromARGB(255, 96, 56, 148), AppColors.primary ], ), borderRadius: BorderRadius.circular(16), ), child: Icon( Icons.assignment_return_outlined, color: Colors.white, size: 24, ), ), SizedBox(width: 20), Text( 'Konfigurasi Refund', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), SpaceHeight(20), Text( 'Pilih Alasan Refund', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.grey[700], ), ), // Reason Selection Grid GridView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 2.5, ), itemCount: refundReasons.length, itemBuilder: (context, index) { final reason = refundReasons[index]; final isSelected = selectedReason == reason['value']; return RefundReasonTile( isSelected: isSelected, reason: reason, onTap: () { setState(() { selectedReason = reason['value']; }); }, ); }, ), if (selectedReason == 'Lainnya') ...[ SpaceHeight(24), TextField( controller: _reasonController, maxLines: 3, decoration: InputDecoration( hintText: 'Jelaskan alasan refund secara detail...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: Colors.grey[300]!), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: AppColors.primary, width: 2), ), filled: true, fillColor: Colors.grey[50], contentPadding: EdgeInsets.all(20), ), ), ], SpaceHeight(32), // Refund Amount Input Text( 'Jumlah Refund', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.grey[700], ), ), SpaceHeight(16), TextField( controller: _refundAmountController, keyboardType: TextInputType.number, readOnly: true, decoration: InputDecoration( hintText: 'Masukkan jumlah refund', prefixText: 'Rp ', prefixStyle: TextStyle( color: AppColors.primary, fontWeight: FontWeight.bold, fontSize: 16, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: Colors.grey[300]!), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: AppColors.primary, width: 2), ), filled: true, fillColor: Colors.grey[50], contentPadding: EdgeInsets.all(20), ), ), ], ), ), ), SpaceHeight(24), // Items Display Card Container( height: 500, // Fixed height untuk items list decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 30, offset: Offset(0, 15), ), ], ), child: Column( children: [ Padding( padding: EdgeInsets.all(20), child: Row( children: [ Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.list_alt, color: Colors.blue[700], size: 24, ), ), SizedBox(width: 16), Expanded( child: Text( 'Item Pesanan', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ), Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(20), ), child: Text( '${widget.selectedOrder.orderItems?.length ?? 0} item', style: TextStyle( color: AppColors.primary, fontWeight: FontWeight.bold, fontSize: 12, ), ), ), ], ), ), // Scrollable Items List Expanded( child: Scrollbar( controller: _itemsScrollController, thumbVisibility: true, child: ListView.builder( controller: _itemsScrollController, padding: EdgeInsets.symmetric(horizontal: 32), itemCount: widget.selectedOrder.orderItems?.length ?? 0, itemBuilder: (context, index) { final item = widget.selectedOrder.orderItems![index]; return RefundOrderItemTile(item: item); }, ), ), ), // Process Refund Button Container( padding: EdgeInsets.all(32), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24), ), ), child: BlocBuilder( builder: (context, state) { final isLoading = state.maybeWhen( loading: () => true, orElse: () => false, ); return SizedBox( width: double.infinity, height: 64, child: ElevatedButton( onPressed: isLoading ? null : () => _processRefund(context), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), elevation: 0, shadowColor: Colors.red.withOpacity(0.3), ), child: isLoading ? Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 24, height: 24, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 3, ), ), SizedBox(width: 16), Text( 'Memproses Refund...', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.monetization_on, color: Colors.white, size: 28), SizedBox(width: 16), Text( 'PROSES REFUND', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: 0.5, ), ), ], ), ), ); }, ), ), ], ), ), SpaceHeight(24), // Extra space for scroll ], ), ); } Widget _buildPaymentRow(String label, int amount, {bool isTotal = false}) { return Padding( padding: EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: isTotal ? 18 : 16, fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, color: isTotal ? AppColors.primary : Colors.grey[700], ), ), Text( amount.currencyFormatRpV2, style: TextStyle( fontSize: isTotal ? 18 : 16, fontWeight: isTotal ? FontWeight.bold : FontWeight.w600, color: isTotal ? AppColors.primary : Colors.grey[800], ), ), ], ), ); } Color _getStatusColor(String? status) { switch (status?.toLowerCase()) { case 'completed': case 'paid': return Colors.green; case 'pending': return Colors.orange; case 'cancelled': return Colors.red; default: return Colors.grey; } } void _processRefund(BuildContext context) { // Validate refund amount final refundAmount = int.tryParse(_refundAmountController.text) ?? 0; if (refundAmount <= 0) { _showErrorDialog('Jumlah refund harus lebih dari 0'); return; } final totalAmount = widget.selectedOrder.totalAmount ?? 0; if (refundAmount > totalAmount) { _showErrorDialog('Jumlah refund tidak boleh melebihi total Pesanan'); return; } // Get reason text String reason = selectedReason; if (selectedReason == 'Lainnya' && _reasonController.text.isNotEmpty) { reason = _reasonController.text; } // Trigger refund event context.read().add( RefundEvent.refundPayment( orderId: widget.selectedOrder.id ?? '', // Assuming order ID is payment ID reason: reason, refundAmount: refundAmount, ), ); } void _showErrorDialog(String message) { showDialog( context: context, builder: (context) => RefundErrorDialog(message: message), ); } void _showSuccessDialog() { final refundAmount = int.tryParse(_refundAmountController.text) ?? 0; showDialog( context: context, barrierDismissible: false, builder: (context) => RefundSuccessDialog( selectedReason: selectedReason, refundAmount: refundAmount, ), ); } }