success void

This commit is contained in:
efrilm
2025-10-31 15:14:28 +07:00
parent 8d9e622121
commit 7961c9d8c5
9 changed files with 906 additions and 79 deletions
@@ -0,0 +1,40 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../application/void/void_form/void_form_bloc.dart';
import '../../../../../common/theme/theme.dart';
import '../../../../components/spaces/space.dart';
import 'widgets/void_success_left_panel.dart';
import 'widgets/void_success_right_panel.dart';
@RoutePage()
class VoidSuccessPage extends StatelessWidget {
const VoidSuccessPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
body: BlocBuilder<VoidFormBloc, VoidFormState>(
builder: (context, state) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Row(
children: [
Expanded(flex: 35, child: VoidSuccessLeftPanel(state: state)),
SpaceWidth(16),
Expanded(
flex: 65,
child: VoidSuccessRightPanel(state: state),
),
],
),
),
);
},
),
);
}
}
@@ -0,0 +1,228 @@
import 'package:auto_route/auto_route.dart';
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/spaces/space.dart';
import '../../../../../router/app_router.gr.dart';
class VoidSuccessLeftPanel extends StatelessWidget {
final VoidFormState state;
const VoidSuccessLeftPanel({super.key, required this.state});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
),
padding: EdgeInsets.all(16),
child: Column(
children: [
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColor.secondaryLight, AppColor.secondaryDark],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: AppColor.secondaryDark.withOpacity(0.2),
blurRadius: 20,
offset: Offset(0, 10),
),
],
),
child: Icon(Icons.check_rounded, color: Colors.white, size: 60),
),
SpaceHeight(16),
Text(
'Void Berhasil!',
style: AppStyle.h1.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.success,
),
textAlign: TextAlign.center,
),
SpaceHeight(8),
Text(
state.voidType.isAll
? 'Seluruh pesanan telah dibatalkan dengan sukses'
: 'Item terpilih telah dibatalkan dengan sukses',
style: AppStyle.lg.copyWith(
fontSize: 16,
color: AppColor.textSecondary,
),
textAlign: TextAlign.center,
),
SpaceHeight(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Informasi Pesanan',
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
),
SpaceHeight(24),
_buildInfoRow(
icon: Icons.receipt_long,
label: 'Order ID',
value: state.order.orderNumber,
),
const SpaceHeight(12),
_buildInfoRow(
icon: Icons.person,
label: 'Pemesan',
value: state.order.metadata['customer_name'] ?? "-",
),
if (state.order.tableNumber != "") ...[
const SpaceHeight(12),
_buildInfoRow(
icon: Icons.table_restaurant_outlined,
label: 'No. Meja',
value: state.order.tableNumber,
),
],
const SpaceHeight(12),
_buildInfoRow(
icon: Icons.category_outlined,
label: 'Tipe Void',
value: state.voidType.isAll
? 'Seluruh Pesanan'
: 'Item Tertentu',
),
const SpaceHeight(12),
_buildInfoRow(
icon: Icons.access_time_rounded,
label: 'Waktu',
value: (DateTime.now()).toFormattedDateTime(),
),
],
),
),
_buildBottomSection(context),
],
),
);
}
Widget _buildBottomSection(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0).copyWith(bottom: 0),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.grey.shade50, Colors.white],
),
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)),
),
child: Column(
children: [
// Action Buttons with Modern Design
Row(
children: [
Expanded(
child: AppElevatedButton.outlined(
onPressed: () {
context.read<VoidFormBloc>().add(
VoidFormEvent.clearState(),
);
context.router.replaceAll([const MainRoute()]);
},
label: "Kembali",
),
),
const SpaceWidth(16),
Expanded(
child: AppElevatedButton.filled(
onPressed: () {
// onPrintRecipt(
// context,
// order: widget.order,
// paymentMethod: widget.paymentMethod,
// nominalBayar: widget.paymentMethod == "Cash"
// ? widget.nominalBayar
// : widget.order.totalAmount ?? 0,
// kembalian: widget.nominalBayar -
// (widget.order.totalAmount ?? 0),
// productQuantity: widget.productQuantity,
// );
// onPrint(
// context,
// productQuantity: widget.productQuantity,
// order: widget.order,
// );
},
label: 'Cetak Struk',
icon: Icon(Icons.print_rounded, color: AppColor.white),
),
),
],
),
],
),
);
}
Widget _buildInfoRow({
required IconData icon,
required String label,
required String value,
Color? valueColor,
bool showBadge = false,
}) {
return Row(
children: [
Icon(icon, size: 18, color: AppColor.primary),
const SizedBox(width: 12),
Text(label, style: AppStyle.md.copyWith(color: AppColor.textSecondary)),
if (showBadge && valueColor != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: valueColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.check_circle, size: 14, color: valueColor),
const SizedBox(width: 4),
Text(
value,
style: AppStyle.sm.copyWith(
fontWeight: FontWeight.bold,
color: valueColor,
),
),
],
),
)
else
Expanded(
child: Text(
value,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.bold,
color: valueColor ?? AppColor.primary,
),
textAlign: TextAlign.right,
),
),
],
);
}
}
@@ -0,0 +1,354 @@
import 'package:flutter/material.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/spaces/space.dart';
class VoidSuccessRightPanel extends StatelessWidget {
final VoidFormState state;
const VoidSuccessRightPanel({super.key, required this.state});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(24),
),
child: Column(
children: [
_header(),
Expanded(
child: ListView.separated(
padding: const EdgeInsets.all(24.0),
itemCount: state.voidItems.length,
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemBuilder: (context, index) {
return _buildProductCard(index);
},
),
),
_buildSummaryFooter(),
],
),
);
}
Widget _buildSummaryFooter() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(24.0),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.grey.shade50, Colors.white],
),
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)),
),
child: Column(
children: [
// Decorative Divider
Container(
height: 1,
margin: const EdgeInsets.only(bottom: 20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.transparent,
AppColor.primary.withOpacity(0.3),
Colors.transparent,
],
),
),
),
// Subtotal Row
Row(
children: [
Row(
children: [
Text(
'Alasan Void: ',
style: AppStyle.md.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w500,
),
),
],
),
SpaceWidth(6),
Expanded(
child: Text(
state.voidReason ?? '',
style: AppStyle.md.copyWith(fontWeight: FontWeight.w600),
textAlign: TextAlign.end,
),
),
],
),
const SpaceHeight(16),
// Total Payment Row with Enhanced Styling
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColor.primary.withOpacity(0.1),
AppColor.primary.withOpacity(0.05),
],
),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: AppColor.primary.withOpacity(0.2),
width: 1,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: AppColor.primary.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
Icons.payments_rounded,
size: 16,
color: AppColor.primary,
),
),
SpaceWidth(12),
Text(
'Total Void',
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
),
],
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColor.primary,
AppColor.primary.withOpacity(0.8),
],
),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: AppColor.primary.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Text(
state.voidType.isAll
? state.order.totalAmount.currencyFormatRpV2
: (state.totalPriceVoid).toString().currencyFormatRpV2,
style: AppStyle.xl.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
),
],
),
);
}
Widget _buildProductCard(int index) {
final item = state.voidItems[index];
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
border: Border.all(color: AppColor.border, width: 1),
),
child: Row(
children: [
// Enhanced Product Image
Container(
width: 70,
height: 70,
decoration: BoxDecoration(
color: AppColor.primaryWithOpacity(0.1),
borderRadius: BorderRadius.circular(16.0),
),
child: Icon(
Icons.restaurant_rounded,
color: AppColor.primary,
size: 28,
),
),
const SpaceWidth(16),
// Product Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.productName,
style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SpaceHeight(6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8),
),
child: Text(
item.unitPrice.currencyFormatRpV2,
style: AppStyle.md.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
const SpaceWidth(16),
// Quantity and Total
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColor.primary,
AppColor.primary.withOpacity(0.8),
],
),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${item.quantity}x',
style: AppStyle.md.copyWith(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
const SpaceHeight(8),
Text(
item.totalPrice.toString().currencyFormatRpV2,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.bold,
color: AppColor.primary,
),
),
],
),
],
),
);
}
Container _header() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: AppColor.border)),
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColor.primary.withOpacity(0.2),
AppColor.primary.withOpacity(0.1),
],
),
borderRadius: BorderRadius.circular(16.0),
),
child: Icon(
Icons.info_outline_rounded,
color: AppColor.primary,
size: 28,
),
),
SpaceWidth(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Detail Void',
style: AppStyle.xxl.copyWith(fontWeight: FontWeight.bold),
),
SpaceHeight(4),
Text(
'Ringkasan item yang divoid',
style: AppStyle.md.copyWith(color: AppColor.textSecondary),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [AppColor.primary, AppColor.primary.withOpacity(0.8)],
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: AppColor.primary.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Text(
state.voidType.isAll
? 'Semua Item'
: '${state.voidItems.length} Items',
style: AppStyle.sm.copyWith(
fontSize: 13,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
],
),
);
}
}
+4 -1
View File
@@ -7,6 +7,7 @@ import '../../../common/theme/theme.dart';
import '../../../domain/order/order.dart';
import '../../components/spaces/space.dart';
import '../../components/toast/flushbar.dart';
import '../../router/app_router.gr.dart';
import 'widgets/void_left_panel.dart';
import 'widgets/void_right_panel.dart';
@@ -26,7 +27,9 @@ class VoidPage extends StatelessWidget implements AutoRouteWrapper {
(either) => either.fold(
(f) => AppFlushbar.showOrderFailureToast(context, f),
(success) {
if (context.mounted) {}
if (context.mounted) {
context.router.replace(VoidSuccessRoute());
}
},
),
);