2025-10-31 14:33:02 +07:00
import 'package:auto_route/auto_route.dart' ;
2026-04-26 22:32:48 +07:00
import 'package:collection/collection.dart' ;
2025-10-31 14:33:02 +07:00
import 'package:flutter/material.dart' ;
import 'package:flutter_bloc/flutter_bloc.dart' ;
import '../../../../application/void/void_form/void_form_bloc.dart' ;
import '../../../../common/extension/extension.dart' ;
import '../../../../common/theme/theme.dart' ;
import '../../../../common/types/void_type.dart' ;
import '../../../components/button/button.dart' ;
import '../../../components/dialog/order/order_void_confirm_dialog.dart' ;
import '../../../components/field/field.dart' ;
import '../../../components/spaces/space.dart' ;
import 'void_radio.dart' ;
class VoidRightPanel extends StatefulWidget {
final VoidFormState state ;
const VoidRightPanel ({ super . key , required this . state });
@ override
State < VoidRightPanel > createState () => _VoidRightPanelState ();
}
class _VoidRightPanelState extends State < VoidRightPanel > {
final ScrollController _scrollController = ScrollController ();
@ override
void dispose () {
super . dispose ();
_scrollController . dispose ();
}
@ override
Widget build ( BuildContext context ) {
return Container (
decoration: BoxDecoration (
color: Colors . white ,
borderRadius: BorderRadius . circular ( 12 ),
),
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Container (
2026-03-01 20:02:12 +07:00
padding: const EdgeInsets . all ( 20 ),
decoration: const BoxDecoration (
2025-10-31 14:33:02 +07:00
border: Border (
bottom: BorderSide ( width: 1 , color: AppColor . border ),
),
borderRadius: BorderRadius . only (
topLeft: Radius . circular ( 12 ),
topRight: Radius . circular ( 12 ),
),
),
child: Row (
children: [
Text (
'Konfigurasi Void' ,
style: AppStyle . xxl . copyWith (
fontWeight: FontWeight . bold ,
color: AppColor . primary ,
),
),
],
),
),
Expanded (
child: Scrollbar (
controller: _scrollController ,
thumbVisibility: true ,
child: SingleChildScrollView (
controller: _scrollController ,
2026-03-01 20:02:12 +07:00
padding: const EdgeInsets . all ( 20 ),
2025-10-31 14:33:02 +07:00
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text (
'Tipe Void *' ,
style: AppStyle . lg . copyWith (
fontWeight: FontWeight . bold ,
color: AppColor . primary ,
),
),
2026-03-01 20:02:12 +07:00
const SpaceHeight ( 12 ),
2025-10-31 14:33:02 +07:00
VoidRadio (
voidType: widget . state . voidType ,
value: VoidType . all ,
title: 'Batalkan Seluruh Pesanan' ,
subtitle: "Batalkan pesanan lengkap dan semua item" ,
onChanged: ( VoidType ? value ) {
context . read < VoidFormBloc > (). add (
2026-03-01 20:02:12 +07:00
const VoidFormEvent . clearSelectedItem (),
2025-10-31 14:33:02 +07:00
);
context . read < VoidFormBloc > (). add (
VoidFormEvent . voidTypeChanged ( value ! ),
);
},
),
2026-03-01 20:02:12 +07:00
const SpaceHeight ( 12 ),
2025-10-31 14:33:02 +07:00
VoidRadio (
voidType: widget . state . voidType ,
value: VoidType . item ,
title: 'Batalkan Barang/Jumlah Tertentu' ,
subtitle:
"Mengurangi atau membatalkan jumlah item tertentu" ,
onChanged: ( VoidType ? value ) {
context . read < VoidFormBloc > (). add (
VoidFormEvent . voidTypeChanged ( value ! ),
);
},
),
2026-03-01 20:02:12 +07:00
const SpaceHeight ( 24 ),
2025-10-31 14:33:02 +07:00
// Selected Items Summary (only show for item void)
if ( widget . state . voidType . isItem ) ...[
Container (
2026-03-01 20:02:12 +07:00
padding: const EdgeInsets . all ( 16 ),
2025-10-31 14:33:02 +07:00
decoration: BoxDecoration (
color: widget . state . selectedItemQuantities . isEmpty
? AppColor . warning . withOpacity ( 0.1 )
: AppColor . success . withOpacity ( 0.1 ),
borderRadius: BorderRadius . circular ( 8 ),
border: Border . all (
color: widget . state . selectedItemQuantities . isEmpty
? AppColor . warning . withOpacity ( 0.6 )
: AppColor . success . withOpacity ( 0.6 ),
),
),
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Row (
children: [
Icon (
widget . state . selectedItemQuantities . isEmpty
? Icons . warning
: Icons . check_circle ,
color:
widget
. state
. selectedItemQuantities
. isEmpty
? AppColor . warning
: AppColor . success ,
size: 20 ,
),
2026-03-01 20:02:12 +07:00
const SpaceWidth ( 8 ),
2025-10-31 14:33:02 +07:00
Expanded (
child: Text (
widget . state . selectedItemQuantities . isEmpty
? 'Silakan pilih item dan jumlah yang akan dibatalkan'
: 'Item yang dipilih untuk dibatalkan:' ,
style: AppStyle . md . copyWith (
fontWeight: FontWeight . w500 ,
color:
widget
. state
. selectedItemQuantities
. isEmpty
? AppColor . warning
: AppColor . success ,
),
),
),
],
),
if ( widget
. state
. selectedItemQuantities
. isNotEmpty ) ...[
2026-03-01 20:02:12 +07:00
const SpaceHeight ( 12 ),
2025-10-31 14:33:02 +07:00
Container (
2026-03-01 20:02:12 +07:00
constraints: const BoxConstraints (
maxHeight: 150 ,
),
2025-10-31 14:33:02 +07:00
child: Scrollbar (
child: SingleChildScrollView (
child: Column (
crossAxisAlignment:
CrossAxisAlignment . start ,
children: widget
. state
. selectedItemQuantities
. entries
. map (( entry ) {
final item = widget
. state
. order
. orderItems
2026-04-26 22:32:48 +07:00
. firstWhereOrNull (
2025-10-31 14:33:02 +07:00
( item ) =>
item . id == entry . key ,
);
2026-04-26 22:32:48 +07:00
if ( item == null ) {
return const SizedBox . shrink ();
}
2025-10-31 14:33:02 +07:00
return Container (
2026-03-01 20:02:12 +07:00
margin: const EdgeInsets . only (
2025-10-31 14:33:02 +07:00
bottom: 8 ,
),
2026-03-01 20:02:12 +07:00
padding: const EdgeInsets . all ( 8 ),
2025-10-31 14:33:02 +07:00
decoration: BoxDecoration (
color: Colors . white ,
borderRadius:
BorderRadius . circular ( 6 ),
border: Border . all (
color: AppColor . success ,
),
),
child: Row (
children: [
Expanded (
child: Column (
crossAxisAlignment:
CrossAxisAlignment
. start ,
children: [
Text (
item . productName ,
style: AppStyle . sm
. copyWith (
fontWeight:
FontWeight
. w500 ,
),
),
Text (
'Qty: ${ entry . value } ' ,
style: AppStyle . xs
. copyWith (
color: AppColor
. textSecondary ,
),
),
],
),
),
Text (
(( item . unitPrice ) *
entry . value )
. currencyFormatRpV2 ,
style: AppStyle . xs . copyWith (
fontWeight:
FontWeight . bold ,
color: AppColor . error ,
),
),
],
),
);
})
. toList (),
),
),
),
),
2026-03-01 20:02:12 +07:00
const Divider ( height: 16 , thickness: 1 ),
2025-10-31 14:33:02 +07:00
Container (
2026-03-01 20:02:12 +07:00
padding: const EdgeInsets . all ( 8 ),
2025-10-31 14:33:02 +07:00
decoration: BoxDecoration (
color: AppColor . error ,
borderRadius: BorderRadius . circular ( 6 ),
),
child: Row (
mainAxisAlignment:
MainAxisAlignment . spaceBetween ,
children: [
Text (
'Jumlah Total Void:' ,
style: AppStyle . md . copyWith (
fontWeight: FontWeight . bold ,
color: Colors . white ,
),
),
Text (
widget
. state
. totalPriceVoid
. currencyFormatRpV2 ,
style: AppStyle . md . copyWith (
fontWeight: FontWeight . bold ,
color: Colors . white ,
),
),
],
),
),
],
],
),
),
2026-03-01 20:02:12 +07:00
const SpaceHeight ( 24 ),
2025-10-31 14:33:02 +07:00
],
Text (
'Alasan Void *' ,
style: AppStyle . lg . copyWith (
fontWeight: FontWeight . bold ,
color: AppColor . primary ,
),
),
2026-03-01 20:02:12 +07:00
const SpaceHeight ( 8 ),
2025-10-31 14:33:02 +07:00
AppTextFormField (
label: 'Harap berikan alasan untuk membatalkan...' ,
showLabel: false ,
maxLines: 4 ,
onChanged: ( value ) {
context . read < VoidFormBloc > (). add (
VoidFormEvent . voidReasonChanged ( value ),
);
},
),
2026-03-01 20:02:12 +07:00
const SpaceHeight ( 32 ),
2025-10-31 14:33:02 +07:00
],
),
),
),
),
// Action Buttons
Padding (
2026-03-01 20:02:12 +07:00
padding: const EdgeInsets . all ( 20 ),
2025-10-31 14:33:02 +07:00
child: Row (
children: [
Expanded (
child: AppElevatedButton . outlined (
onPressed: () => context . router . maybePop (),
label: 'Batal' ,
),
),
2026-03-01 20:02:12 +07:00
const SpaceWidth ( 12 ),
2025-10-31 14:33:02 +07:00
Expanded (
child: AppElevatedButton . filled (
onPressed: _canProcessVoid () ? _processVoid : null ,
label: widget . state . voidType . isAll
? 'Void Pesanan'
: 'Void Produk' ,
),
),
],
),
),
],
),
);
}
bool _canProcessVoid () {
if ( widget . state . voidReason ? . isEmpty ?? true ) return false ;
if ( widget . state . voidType . isItem &&
widget . state . selectedItemQuantities . isEmpty ) {
return false ;
}
return true ;
}
void _processVoid () {
String confirmMessage ;
if ( widget . state . voidType . isAll ) {
confirmMessage =
'Apakah Anda yakin ingin membatalkan seluruh pesanan # ${ widget . state . order . orderNumber } ? \n\n Ini akan membatalkan semua item dalam pesanan.' ;
} else {
int totalItems = widget . state . selectedItemQuantities . values . fold (
0 ,
( sum , qty ) => sum + qty ,
);
confirmMessage =
'Apakah Anda yakin ingin membatalkan $ totalItems item dari pesanan # ${ widget . state . order . orderNumber } ? \n\n Jumlah Batal: ${ widget . state . totalPriceVoid . currencyFormatRpV2 } ' ;
}
showDialog (
context: context ,
barrierDismissible: false ,
builder: ( BuildContext context ) {
return OrderVoidConfirmDialog ( message: confirmMessage );
},
);
}
}