import 'package:enaklo_pos/core/components/buttons.dart'; import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/extensions/build_context_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/void/bloc/void_order_bloc.dart'; import 'package:enaklo_pos/presentation/void/dialog/confirm_void_dialog.dart'; import 'package:enaklo_pos/presentation/void/pages/success_void_page.dart'; import 'package:enaklo_pos/presentation/void/widgets/product_card.dart'; import 'package:enaklo_pos/presentation/void/widgets/void_radio.dart'; import 'package:enaklo_pos/presentation/void/widgets/void_loading.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class VoidPage extends StatefulWidget { final Order selectedOrder; const VoidPage({super.key, required this.selectedOrder}); @override State createState() => _VoidPageState(); } class _VoidPageState extends State { String voidType = 'all'; // 'all' or 'item' Map selectedItemQuantities = {}; // itemId -> voidQuantity String voidReason = ''; final TextEditingController reasonController = TextEditingController(); final ScrollController _leftPanelController = ScrollController(); final ScrollController _rightPanelController = ScrollController(); @override void dispose() { _leftPanelController.dispose(); _rightPanelController.dispose(); reasonController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { state.when( initial: () {}, loading: () { // Show loading indicator if needed }, success: () { context.pushReplacement( SuccessVoidPage( voidedOrder: widget.selectedOrder, voidType: voidType, voidAmount: _calculateVoidAmount(), voidReason: voidReason, voidedItems: voidType == 'item' ? _getVoidedItems() : null, ), ); }, error: (message) { _showErrorDialog(message); }, ); }, child: Scaffold( backgroundColor: Colors.grey[100], body: BlocBuilder( builder: (context, state) { return Stack( children: [ OrientationBuilder( builder: (context, orientation) { return Padding( padding: EdgeInsets.all(24.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Left Panel - Order Details & Items Expanded( flex: 3, child: _buildOrderDetailsPanel(), ), SpaceWidth(24), // Right Panel - Void Configuration Expanded( flex: 2, child: _buildVoidConfigPanel(), ), ], ), ); }, ), // Loading Overlay state.when( initial: () => SizedBox.shrink(), loading: () => VoidLoading(), success: () => SizedBox.shrink(), error: (message) => SizedBox.shrink(), ), ], ); }, ), ), ); } Widget _buildOrderDetailsPanel() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Order Header - Fixed Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Row( children: [ Icon(Icons.receipt_long, color: AppColors.primary, size: 24), SpaceWidth(12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Pesanan #${widget.selectedOrder.orderNumber}', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), Text( 'Meja: ${widget.selectedOrder.tableNumber ?? 'N/A'} • ${widget.selectedOrder.orderType ?? 'N/A'}', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), ], ), ), Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: _getStatusColor(widget.selectedOrder.status) .withOpacity(0.2), borderRadius: BorderRadius.circular(16), ), child: Text( widget.selectedOrder.status?.toUpperCase() ?? 'UNKNOWN', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: _getStatusColor(widget.selectedOrder.status), ), ), ), ], ), ), // Scrollable Content Expanded( child: Scrollbar( controller: _leftPanelController, thumbVisibility: true, child: SingleChildScrollView( controller: _leftPanelController, padding: EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Order Summary - Fixed in scroll Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[300]!), ), child: Column( children: [ _buildSummaryRow( 'Subtotal:', (widget.selectedOrder.subtotal ?? 0) .currencyFormatRpV2), _buildSummaryRow( 'Pajak:', (widget.selectedOrder.taxAmount ?? 0) .currencyFormatRpV2), _buildSummaryRow('Diskon:', '- ${(widget.selectedOrder.discountAmount ?? 0).currencyFormatRpV2}'), Divider(thickness: 1), _buildSummaryRow( 'Total:', (widget.selectedOrder.totalAmount ?? 0) .currencyFormatRpV2, isTotal: true, ), if (voidType == 'item' && selectedItemQuantities.isNotEmpty) ...[ Divider(thickness: 1, color: Colors.red[300]), _buildSummaryRow( 'Total Void:', '- ${(_calculateVoidAmount().currencyFormatRpV2)}', isVoid: true, ), ], ], ), ), SpaceHeight(24), // Order Items Section Title Row( children: [ Icon(Icons.shopping_cart, color: AppColors.primary, size: 20), SpaceWidth(8), Text( 'Produk Pesanan (${_getPendingItem().length})', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), SpaceHeight(16), // Order Items List - Scrollable ...List.generate( _getPendingItem().length, (index) { final item = _getPendingItem()[index]; final voidQty = selectedItemQuantities[item.id] ?? 0; final isSelected = voidQty > 0; final canSelect = voidType == 'item'; return VoidProductCard( isSelected: isSelected, item: item, voidQty: voidQty, canSelect: canSelect, onTapDecrease: voidQty > 0 ? () { setState(() { if (voidQty == 1) { selectedItemQuantities.remove(item.id); } else { selectedItemQuantities[item.id!] = voidQty - 1; } }); } : null, onTapIncrease: voidQty < (item.quantity ?? 0) ? () { setState(() { selectedItemQuantities[item.id!] = voidQty + 1; }); } : null, onTapAll: () { setState(() { selectedItemQuantities[item.id!] = item.quantity ?? 0; }); }, onTapClear: voidQty > 0 ? () { setState(() { selectedItemQuantities.remove(item.id); }); } : null, ); }, ), ], ), ), ), ), ], ), ); } Widget _buildVoidConfigPanel() { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header - Fixed Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), borderRadius: BorderRadius.only( topLeft: Radius.circular(12), topRight: Radius.circular(12), ), ), child: Row( children: [ Icon(Icons.cancel, color: Colors.red, size: 24), SpaceWidth(12), Text( 'Konfigurasi Void', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.red, ), ), ], ), ), // Scrollable Content Expanded( child: Scrollbar( controller: _rightPanelController, thumbVisibility: true, child: SingleChildScrollView( controller: _rightPanelController, padding: EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Void Type Selection Text( 'Tipe Void *', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), SpaceHeight(12), // Void All Option VoidRadio( voidType: voidType, value: 'all', title: 'Batalkan Seluruh Pesanan', subtitle: "Batalkan pesanan lengkap dan semua item", onChanged: (String? value) { setState(() { voidType = value!; selectedItemQuantities.clear(); }); }, ), SpaceHeight(12), // Void Items Option VoidRadio( voidType: voidType, value: 'item', title: 'Batalkan Barang/Jumlah Tertentu', subtitle: "Mengurangi atau membatalkan jumlah item tertentu", onChanged: (String? value) { setState(() { voidType = value!; selectedItemQuantities.clear(); }); }, ), SpaceHeight(24), // Selected Items Summary (only show for item void) if (voidType == 'item') ...[ Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: selectedItemQuantities.isEmpty ? Colors.orange[50] : Colors.green[50], borderRadius: BorderRadius.circular(8), border: Border.all( color: selectedItemQuantities.isEmpty ? Colors.orange[300]! : Colors.green[300]!, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( selectedItemQuantities.isEmpty ? Icons.warning : Icons.check_circle, color: selectedItemQuantities.isEmpty ? Colors.orange[700] : Colors.green[700], size: 20, ), SpaceWidth(8), Expanded( child: Text( selectedItemQuantities.isEmpty ? 'Silakan pilih item dan jumlah yang akan dibatalkan' : 'Item yang dipilih untuk dibatalkan:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: selectedItemQuantities.isEmpty ? Colors.orange[700] : Colors.green[700], ), ), ), ], ), if (selectedItemQuantities.isNotEmpty) ...[ SpaceHeight(12), Container( constraints: BoxConstraints(maxHeight: 150), child: Scrollbar( child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: selectedItemQuantities.entries .map((entry) { final item = widget .selectedOrder.orderItems! .firstWhere( (item) => item.id == entry.key); return Container( margin: EdgeInsets.only(bottom: 8), padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(6), border: Border.all( color: Colors.green[200]!), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.productName ?? 'Unknown', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, ), ), Text( 'Qty: ${entry.value}', style: TextStyle( fontSize: 11, color: Colors.grey[600], ), ), ], ), ), Text( ((item.unitPrice ?? 0) * entry.value) .currencyFormatRpV2, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.red[700], ), ), ], ), ); }).toList(), ), ), ), ), Divider(height: 16, thickness: 1), Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.red.withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Jumlah Total Void:', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), Text( _calculateVoidAmount().currencyFormatRpV2, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.red[700], ), ), ], ), ), ], ], ), ), SpaceHeight(24), ], // Void Reason Text( 'Alasan Void *', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), SpaceHeight(8), TextField( controller: reasonController, maxLines: 4, decoration: InputDecoration( hintText: 'Harap berikan alasan untuk membatalkan...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey[300]!), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: AppColors.primary, width: 2), ), contentPadding: EdgeInsets.all(16), ), onChanged: (value) { setState(() { voidReason = value; }); }, ), SpaceHeight(32), // Action Buttons Row( children: [ Expanded( child: Button.outlined( onPressed: () => context.pop(), label: 'Batal', ), ), SpaceWidth(12), Expanded( child: Button.filled( onPressed: _canProcessVoid() ? _processVoid : null, label: voidType == 'all' ? 'Void Pesanan' : 'Void Produk', ), ), ], ), ], ), ), ), ), ], ), ); } Widget _buildSummaryRow(String label, String value, {bool isTotal = false, bool isVoid = false}) { return Padding( padding: EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle( fontSize: isTotal ? 16 : 14, fontWeight: isTotal ? FontWeight.bold : FontWeight.normal, color: isVoid ? Colors.red : (isTotal ? AppColors.primary : Colors.grey[700]), ), ), Text( value, style: TextStyle( fontSize: isTotal ? 16 : 14, fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, color: isVoid ? Colors.red : (isTotal ? AppColors.primary : Colors.grey[700]), ), ), ], ), ); } Color _getStatusColor(String? status) { switch (status?.toLowerCase()) { case 'completed': return Colors.green; case 'pending': return Colors.orange; case 'cancelled': return Colors.red; case 'processing': return Colors.blue; default: return Colors.grey; } } int _calculateVoidAmount() { int total = 0; selectedItemQuantities.forEach((itemId, voidQty) { final item = widget.selectedOrder.orderItems! .firstWhere((item) => item.id == itemId); total += (item.unitPrice ?? 0) * voidQty; }); return total; } bool _canProcessVoid() { if (voidReason.trim().isEmpty) return false; if (voidType == 'item' && selectedItemQuantities.isEmpty) return false; return true; } List _getPendingItem() { return widget.selectedOrder.orderItems! .where((item) => item.status == 'pending') .toList(); } List _getVoidedItems() { List voidedItems = []; selectedItemQuantities.forEach((itemId, voidQty) { final originalItem = widget.selectedOrder.orderItems! .firstWhere((item) => item.id == itemId); // Buat OrderItem baru dengan quantity yang di-void voidedItems.add(OrderItem( id: originalItem.id, orderId: originalItem.orderId, productId: originalItem.productId, productName: originalItem.productName, productVariantId: originalItem.productVariantId, productVariantName: originalItem.productVariantName, quantity: voidQty, // Hanya quantity yang di-void unitPrice: originalItem.unitPrice, totalPrice: (originalItem.unitPrice ?? 0) * voidQty, modifiers: originalItem.modifiers, notes: originalItem.notes, status: originalItem.status, createdAt: originalItem.createdAt, updatedAt: originalItem.updatedAt, )); }); return voidedItems; } void _processVoid() { String confirmMessage; if (voidType == 'all') { confirmMessage = 'Apakah Anda yakin ingin membatalkan seluruh pesanan #${widget.selectedOrder.orderNumber}?\n\nIni akan membatalkan semua item dalam pesanan.'; } else { int totalItems = selectedItemQuantities.values.fold(0, (sum, qty) => sum + qty); confirmMessage = 'Apakah Anda yakin ingin membatalkan $totalItems item dari pesanan #${widget.selectedOrder.orderNumber}?\n\nJumlah Batal: ${(_calculateVoidAmount()).currencyFormatRpV2}'; } showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return ConfirmVoidDialog( message: confirmMessage, onTap: _performVoid, order: widget.selectedOrder, voidType: voidType, selectedItemQuantities: selectedItemQuantities, voidReason: voidReason, ); }, ); } void _performVoid() { // Prepare order items for void List voidItems = []; if (voidType == 'item') { selectedItemQuantities.forEach((itemId, voidQty) { final originalItem = widget.selectedOrder.orderItems! .firstWhere((item) => item.id == itemId); // Create new OrderItem with void quantity voidItems.add(OrderItem( id: originalItem.id, orderId: originalItem.orderId, productId: originalItem.productId, productName: originalItem.productName, productVariantId: originalItem.productVariantId, productVariantName: originalItem.productVariantName, quantity: voidQty, // This is the void quantity unitPrice: originalItem.unitPrice, totalPrice: (originalItem.unitPrice ?? 0) * voidQty, modifiers: originalItem.modifiers, notes: originalItem.notes, status: originalItem.status, createdAt: originalItem.createdAt, updatedAt: originalItem.updatedAt, )); }); } // Trigger void order event context.read().add( VoidOrderEvent.voidOrder( orderId: widget.selectedOrder.id!, reason: voidReason, type: voidType.toUpperCase(), orderItems: voidItems, ), ); } void _showErrorDialog(String message) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: Row( children: [ Icon(Icons.error, color: Colors.red), SpaceWidth(8), Text('Void Gagal'), ], ), content: Text(message), actions: [ ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: Colors.white, ), child: Text('OK'), ), ], ); }, ); } }