feat: refund page
This commit is contained in:
@@ -0,0 +1,818 @@
|
||||
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<RefundPage> createState() => _RefundPageState();
|
||||
}
|
||||
|
||||
class _RefundPageState extends State<RefundPage> 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<Offset> _slideAnimation;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
|
||||
final List<Map<String, dynamic>> refundReasons = [
|
||||
{'value': 'Barang Rusak', 'icon': Icons.broken_image, 'color': Colors.red},
|
||||
{'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.grey},
|
||||
];
|
||||
|
||||
@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<Offset>(
|
||||
begin: Offset(0.0, 1.0),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _slideController,
|
||||
curve: Curves.elasticOut,
|
||||
));
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _fadeController,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
_scaleAnimation = Tween<double>(
|
||||
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<RefundBloc, RefundState>(
|
||||
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),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 30,
|
||||
offset: Offset(0, 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.green[400]!, Colors.green[600]!],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.green.withOpacity(0.3),
|
||||
blurRadius: 15,
|
||||
offset: Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
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),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 30,
|
||||
offset: Offset(0, 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.account_balance_wallet,
|
||||
color: Colors.amber[700],
|
||||
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),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 30,
|
||||
offset: Offset(0, 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Colors.red[400]!, Colors.red[600]!],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.red.withOpacity(0.3),
|
||||
blurRadius: 15,
|
||||
offset: Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Icon(
|
||||
Icons.assignment_return,
|
||||
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<RefundBloc, RefundState>(
|
||||
builder: (context, state) {
|
||||
final isLoading = state.maybeWhen(
|
||||
loading: () => true,
|
||||
orElse: () => false,
|
||||
);
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 64,
|
||||
child: ElevatedButton(
|
||||
onPressed:
|
||||
isLoading ? null : () => _processRefund(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red[600],
|
||||
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<RefundBloc>().add(
|
||||
RefundEvent.refundPayment(
|
||||
paymentId: 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user