feat: success save order page add fix save order

This commit is contained in:
efrilm 2025-08-03 18:24:13 +07:00
parent 41db40eb54
commit f581c17749
13 changed files with 397 additions and 64 deletions

View File

@ -0,0 +1,48 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:flutter/material.dart';
class AppFlushbar {
static void showSuccess(BuildContext context, String message) {
Flushbar(
messageText: Text(
message,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
icon: const Icon(
Icons.check_circle,
color: Colors.white,
),
duration: const Duration(seconds: 2),
flushbarPosition: FlushbarPosition.BOTTOM,
backgroundColor: Colors.green,
borderRadius: BorderRadius.circular(12),
margin: const EdgeInsets.all(12),
).show(context);
}
static void showError(BuildContext context, String message) {
Flushbar(
messageText: Text(
message,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
icon: const Icon(
Icons.error,
color: Colors.white,
),
duration: const Duration(seconds: 3),
flushbarPosition: FlushbarPosition.BOTTOM,
backgroundColor: Colors.red,
borderRadius: BorderRadius.circular(12),
margin: const EdgeInsets.all(12),
).show(context);
}
}

View File

@ -191,7 +191,8 @@ class OrderRemoteDatasource {
}
// New Api
Future<Either<String, bool>> createOrder(OrderRequestModel orderModel) async {
Future<Either<String, OrderDetailResponseModel>> createOrder(
OrderRequestModel orderModel) async {
final authData = await AuthLocalDataSource().getAuthData();
final url = '${Variables.baseUrl}/api/v1/orders';
@ -209,7 +210,7 @@ class OrderRemoteDatasource {
);
if (response.statusCode == 200) {
return const Right(true);
return Right(OrderDetailResponseModel.fromMap(response.data));
} else {
return const Left('Gagal membuat pesanan');
}

View File

@ -30,6 +30,36 @@ class OrderResponseModel {
};
}
class OrderDetailResponseModel {
final bool? success;
final Order? data;
final dynamic errors;
OrderDetailResponseModel({
this.success,
this.data,
this.errors,
});
factory OrderDetailResponseModel.fromJson(String str) =>
OrderDetailResponseModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory OrderDetailResponseModel.fromMap(Map<String, dynamic> json) =>
OrderDetailResponseModel(
success: json["success"],
data: json["data"] == null ? null : Order.fromMap(json["data"]),
errors: json["errors"],
);
Map<String, dynamic> toMap() => {
"success": success,
"data": data?.toMap(),
"errors": errors,
};
}
class OrderData {
final List<Order>? orders;
final int? totalCount;

View File

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/order_request.dart';
import 'package:enaklo_pos/presentation/home/models/order_type.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
@ -47,7 +48,7 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
result.fold(
(error) => emit(_Error(error)),
(success) => emit(const _Success()),
(success) => emit(_Success(success.data!)),
);
} catch (e) {
log("Error in AddOrderItemsBloc: $e");

View File

@ -327,7 +327,7 @@ mixin _$OrderFormState {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(Order order) success,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@ -335,7 +335,7 @@ mixin _$OrderFormState {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(Order order)? success,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@ -343,7 +343,7 @@ mixin _$OrderFormState {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(Order order)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
@ -439,7 +439,7 @@ class _$InitialImpl implements _Initial {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(Order order) success,
required TResult Function(String message) error,
}) {
return initial();
@ -450,7 +450,7 @@ class _$InitialImpl implements _Initial {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(Order order)? success,
TResult? Function(String message)? error,
}) {
return initial?.call();
@ -461,7 +461,7 @@ class _$InitialImpl implements _Initial {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(Order order)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
@ -556,7 +556,7 @@ class _$LoadingImpl implements _Loading {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(Order order) success,
required TResult Function(String message) error,
}) {
return loading();
@ -567,7 +567,7 @@ class _$LoadingImpl implements _Loading {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(Order order)? success,
TResult? Function(String message)? error,
}) {
return loading?.call();
@ -578,7 +578,7 @@ class _$LoadingImpl implements _Loading {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(Order order)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
@ -635,6 +635,8 @@ abstract class _$$SuccessImplCopyWith<$Res> {
factory _$$SuccessImplCopyWith(
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
__$$SuccessImplCopyWithImpl<$Res>;
@useResult
$Res call({Order order});
}
/// @nodoc
@ -647,36 +649,61 @@ class __$$SuccessImplCopyWithImpl<$Res>
/// Create a copy of OrderFormState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? order = null,
}) {
return _then(_$SuccessImpl(
null == order
? _value.order
: order // ignore: cast_nullable_to_non_nullable
as Order,
));
}
}
/// @nodoc
class _$SuccessImpl implements _Success {
const _$SuccessImpl();
const _$SuccessImpl(this.order);
@override
final Order order;
@override
String toString() {
return 'OrderFormState.success()';
return 'OrderFormState.success(order: $order)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$SuccessImpl);
(other.runtimeType == runtimeType &&
other is _$SuccessImpl &&
(identical(other.order, order) || other.order == order));
}
@override
int get hashCode => runtimeType.hashCode;
int get hashCode => Object.hash(runtimeType, order);
/// Create a copy of OrderFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
__$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(Order order) success,
required TResult Function(String message) error,
}) {
return success();
return success(order);
}
@override
@ -684,10 +711,10 @@ class _$SuccessImpl implements _Success {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(Order order)? success,
TResult? Function(String message)? error,
}) {
return success?.call();
return success?.call(order);
}
@override
@ -695,12 +722,12 @@ class _$SuccessImpl implements _Success {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(Order order)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (success != null) {
return success();
return success(order);
}
return orElse();
}
@ -744,7 +771,15 @@ class _$SuccessImpl implements _Success {
}
abstract class _Success implements OrderFormState {
const factory _Success() = _$SuccessImpl;
const factory _Success(final Order order) = _$SuccessImpl;
Order get order;
/// Create a copy of OrderFormState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
@ -817,7 +852,7 @@ class _$ErrorImpl implements _Error {
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(Order order) success,
required TResult Function(String message) error,
}) {
return error(message);
@ -828,7 +863,7 @@ class _$ErrorImpl implements _Error {
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(Order order)? success,
TResult? Function(String message)? error,
}) {
return error?.call(message);
@ -839,7 +874,7 @@ class _$ErrorImpl implements _Error {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(Order order)? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {

View File

@ -4,6 +4,6 @@ part of 'order_form_bloc.dart';
class OrderFormState with _$OrderFormState {
const factory OrderFormState.initial() = _Initial;
const factory OrderFormState.loading() = _Loading;
const factory OrderFormState.success() = _Success;
const factory OrderFormState.success(Order order) = _Success;
const factory OrderFormState.error(String message) = _Error;
}

View File

@ -1,5 +1,6 @@
import 'package:enaklo_pos/core/components/buttons.dart';
import 'package:enaklo_pos/core/components/custom_modal_dialog.dart';
import 'package:enaklo_pos/core/components/flushbar.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';
@ -8,6 +9,7 @@ import 'package:enaklo_pos/presentation/home/bloc/get_table_status/get_table_sta
import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/order_type.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/success/pages/success_save_order_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -67,10 +69,6 @@ class _PaymentSaveDialogState extends State<PaymentSaveDialog> {
return state.maybeWhen(
orElse: () => const CircularProgressIndicator(),
success: (tables) {
print("🔘 Tables fetched: ${tables.length} tables");
print(
"🔘 Table statuses: ${tables.map((t) => '${t.tableName}: ${t.status}').join(', ')}");
// No need to filter since we're fetching the correct tables directly
final availableTables = tables;
if (selectTable == null && availableTables.isNotEmpty) {
@ -132,18 +130,40 @@ class _PaymentSaveDialogState extends State<PaymentSaveDialog> {
],
),
SpaceHeight(24),
Button.filled(
onPressed: () {
context.read<OrderFormBloc>().add(
OrderFormEvent.create(
items: widget.items,
customerName: widget.customerName,
orderType: widget.orderType,
tableNumber: selectTable!.tableName.toString(),
),
);
BlocListener<OrderFormBloc, OrderFormState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: (data) {
context.pushReplacement(SuccessSaveOrderPage(
order: data,
));
},
error: (message) => AppFlushbar.showError(context, message),
);
},
label: "Simpan",
child: BlocBuilder<OrderFormBloc, OrderFormState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => Button.filled(
onPressed: () {
context.read<OrderFormBloc>().add(
OrderFormEvent.create(
items: widget.items,
customerName: widget.customerName,
orderType: widget.orderType,
tableNumber: selectTable!.tableName.toString(),
),
);
},
label: "Simpan",
),
loading: () => Center(
child: const CircularProgressIndicator(),
),
);
},
),
),
],
),

View File

@ -1,6 +1,7 @@
import 'package:enaklo_pos/core/components/custom_modal_dialog.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/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/dialog/payment_add_order_dialog.dart';
import 'package:enaklo_pos/presentation/home/dialog/payment_save_dialog.dart';
@ -8,7 +9,7 @@ import 'package:enaklo_pos/presentation/home/models/order_type.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:flutter/material.dart';
class SaveDialog extends StatelessWidget {
class SaveDialog extends StatefulWidget {
final TableModel? selectedTable;
final String customerName;
final OrderType orderType;
@ -21,6 +22,11 @@ class SaveDialog extends StatelessWidget {
required this.orderType,
required this.items});
@override
State<SaveDialog> createState() => _SaveDialogState();
}
class _SaveDialogState extends State<SaveDialog> {
@override
Widget build(BuildContext context) {
return CustomModalDialog(
@ -31,18 +37,20 @@ class SaveDialog extends StatelessWidget {
child: Column(
children: [
_item(
icon: Icons.schedule_outlined,
title: 'Bayar Nanti',
subtitle: 'Simpan pesanan dan bayar nanti',
onTap: () => showDialog(
context: context,
builder: (context) => PaymentSaveDialog(
selectedTable: selectedTable,
customerName: customerName,
orderType: orderType,
items: items),
),
),
icon: Icons.schedule_outlined,
title: 'Bayar Nanti',
subtitle: 'Simpan pesanan dan bayar nanti',
onTap: () {
context.pop();
showDialog(
context: context,
builder: (context) => PaymentSaveDialog(
selectedTable: widget.selectedTable,
customerName: widget.customerName,
orderType: widget.orderType,
items: widget.items),
);
}),
SpaceHeight(16.0),
_item(
icon: Icons.shopping_cart_checkout_outlined,

View File

@ -883,14 +883,14 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
Expanded(
child: Button.filled(
onPressed: () => showDialog(
context: context,
builder: (context) => SaveDialog(
selectedTable: widget.table,
customerName:
customerController.text,
items: items,
orderType: orderType,
)),
context: context,
builder: (dcontext) => SaveDialog(
selectedTable: widget.table,
customerName: customerController.text,
items: items,
orderType: orderType,
),
),
label: 'Simpan',
),
),

View File

@ -1,5 +1,4 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart';

View File

@ -0,0 +1,182 @@
import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/components/dashed_divider.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/date_time_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart';
import 'package:flutter/material.dart';
class SuccessSaveOrderPage extends StatelessWidget {
final Order order;
const SuccessSaveOrderPage({super.key, required this.order});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: Center(
child: Container(
width: context.deviceWidth * 0.4,
height: context.deviceHeight * 0.8,
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'Pesanan!',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
Text('Pesanan Berhasil disimpan',
style: const TextStyle(fontSize: 14)),
],
),
),
DashedDivider(
color: AppColors.grey,
),
SpaceHeight(24),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
order.metadata?['customer_name'] ?? "-",
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Padding(
padding: const EdgeInsets.all(16.0).copyWith(top: 24),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'No. Pesanan',
),
Text(
order.orderNumber ?? "-",
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
SpaceHeight(4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'No. Meja',
),
Text(
order.tableNumber ?? "-",
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
SpaceHeight(4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Waktu',
),
Text(
(order.createdAt ?? DateTime.now())
.toFormattedDate3(),
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
],
),
),
Spacer(),
DashedDivider(
color: AppColors.grey,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Status Pembayaran',
),
Text(
'Belum Bayar',
style: const TextStyle(
fontWeight: FontWeight.bold, color: Colors.red),
),
],
),
),
DashedDivider(
color: AppColors.grey,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total Pembayaran',
),
Text(
(order.totalAmount ?? 0).toString().currencyFormatRpV2,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
DashedDivider(
color: AppColors.grey,
),
Spacer(),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: Button.outlined(
onPressed: () =>
context.pushReplacement(DashboardPage()),
label: 'Kembali',
height: 44,
),
),
SpaceWidth(12),
Expanded(
child: Button.filled(
onPressed: () {},
label: 'Cetak',
icon: Icon(
Icons.print,
color: AppColors.white,
),
height: 44,
),
),
],
),
),
],
),
),
),
);
}
}

View File

@ -22,6 +22,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.11.0"
another_flushbar:
dependency: "direct main"
description:
name: another_flushbar
sha256: "19bf9520230ec40b300aaf9dd2a8fefcb277b25ecd1c4838f530566965befc2a"
url: "https://pub.dev"
source: hosted
version: "1.12.30"
archive:
dependency: transitive
description:

View File

@ -61,6 +61,7 @@ dependencies:
flutter_screenutil: ^5.9.3
dio: ^5.8.0+1
awesome_dio_interceptor: ^1.3.0
another_flushbar: ^1.12.30
# imin_printer: ^0.6.10
dev_dependencies: