Compare commits

...
31 Commits
Author SHA1 Message Date
Efril 5d73f82017 fix payment struck 2026-01-13 23:41:56 +07:00
aefril 1d52f22f5f Merge pull request 'dev' (#1) from dev into main
Reviewed-on: #1
2025-08-13 17:19:46 +00:00
efrilm f87309af3f feat: update 2025-08-14 00:16:42 +07:00
efrilm 96d0b91984 feat: succes payment 2025-08-14 00:12:49 +07:00
efrilm e20dc53fb8 feat: profit loss widget 2025-08-13 23:27:52 +07:00
efrilm c09822b470 feat: success save order 2025-08-13 23:08:12 +07:00
efrilm 6a9a26f71c fix: success save order 2025-08-13 23:00:11 +07:00
efrilm b0006ee6bc fix: order and add to order 2025-08-13 22:52:37 +07:00
efrilm b34428965e fix: bug 2025-08-13 22:11:24 +07:00
efrilm 670a0b6414 feat: update split bill 2025-08-08 23:04:35 +07:00
efrilm b7ab9c6939 feat: update logo 2025-08-08 19:44:00 +07:00
efrilm 6b5af82307 feat: table 2025-08-08 14:59:49 +07:00
efrilm 2848678cdc fix: amount 2025-08-08 14:39:56 +07:00
efrilm f05347c90d feat: transfer table 2025-08-08 14:22:12 +07:00
efrilm 94725964ea fix: change position table 2025-08-08 13:45:03 +07:00
efrilm 2eceb0bc0c feat: update 2025-08-08 10:53:46 +07:00
efrilm e19d788f47 feat: update order detail 2025-08-08 10:29:17 +07:00
efrilm 3a07d2e7cc feat: fix payment 2025-08-08 00:12:10 +07:00
efrilm 2c8b1333d8 feat: order response model 2025-08-07 23:59:01 +07:00
efrilm b1a1303a40 feat: split add customer optional 2025-08-07 22:45:06 +07:00
efrilm de22251440 feat: update print 2025-08-07 22:03:05 +07:00
efrilm 203eda75e4 feat: save outlet to local 2025-08-07 21:43:13 +07:00
efrilm 49fef5afb2 fix: ui refund and sales page 2025-08-07 20:11:57 +07:00
efrilm e3da008a9a fix: customer ui 2025-08-07 19:59:54 +07:00
efrilm b5ad20eb8b feat: handler ui and update void ui 2025-08-07 19:57:59 +07:00
efrilm fbd59964c3 feat: update dashboard 2025-08-07 15:47:38 +07:00
efrilm 5a260ca3a1 feat: ticket printer 2025-08-07 15:30:05 +07:00
efrilm ffe076e120 feat: payment split 2025-08-07 14:48:59 +07:00
efrilm 9d78c9dcce fix: print 2025-08-07 14:28:52 +07:00
efrilm d0e1f9a5b6 feat: update success payment page 2025-08-07 12:49:42 +07:00
efrilm ab2a59930c feat: order search 2025-08-07 12:34:57 +07:00
110 changed files with 6067 additions and 1635 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ android {
applicationId "com.appscale.pos"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 21
minSdkVersion flutter.minSdkVersion
targetSdkVersion 35
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 983 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 19 KiB

+10
View File
@@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_17_4760)">
<path d="M9 13.75C6.66 13.75 2 14.92 2 17.25V19H16V17.25C16 14.92 11.34 13.75 9 13.75ZM4.34 17C5.18 16.42 7.21 15.75 9 15.75C10.79 15.75 12.82 16.42 13.66 17H4.34ZM9 12C10.93 12 12.5 10.43 12.5 8.5C12.5 6.57 10.93 5 9 5C7.07 5 5.5 6.57 5.5 8.5C5.5 10.43 7.07 12 9 12ZM9 7C9.83 7 10.5 7.67 10.5 8.5C10.5 9.33 9.83 10 9 10C8.17 10 7.5 9.33 7.5 8.5C7.5 7.67 8.17 7 9 7ZM16.04 13.81C17.2 14.65 18 15.77 18 17.25V19H22V17.25C22 15.23 18.5 14.08 16.04 13.81ZM15 12C16.93 12 18.5 10.43 18.5 8.5C18.5 6.57 16.93 5 15 5C14.46 5 13.96 5.13 13.5 5.35C14.13 6.24 14.5 7.33 14.5 8.5C14.5 9.67 14.13 10.76 13.5 11.65C13.96 11.87 14.46 12 15 12Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_17_4760">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 804 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+4 -4
View File
@@ -3,15 +3,15 @@
flutter_launcher_icons:
android: "launcher_icon"
ios: true
image_path: "assets/logo/ic_launcher.png"
image_path: "assets/logo/logo.png"
remove_alpha_ios: true
min_sdk_android: 21 # android min sdk min:16, default 21
adaptive_icon_background: "#ffffff"
adaptive_icon_foreground: "assets/logo/ic_launcher.png"
adaptive_icon_foreground: "assets/logo/logo.png"
web:
generate: true
image_path: "assets/logo/ic_launcher.png"
image_path: "assets/logo/logo.png"
windows:
generate: true
image_path: "assets/logo/ic_launcher.png"
image_path: "assets/logo/logo.png"
icon_size: 48
+8 -17
View File
@@ -101,6 +101,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/payments.svg
SvgGenImage get payments => const SvgGenImage('assets/icons/payments.svg');
/// File path: assets/icons/people.svg
SvgGenImage get people => const SvgGenImage('assets/icons/people.svg');
/// File path: assets/icons/print.svg
SvgGenImage get print => const SvgGenImage('assets/icons/print.svg');
@@ -152,6 +155,7 @@ class $AssetsIconsGen {
orders,
pajak,
payments,
people,
print,
qrCode,
report,
@@ -300,12 +304,8 @@ class $AssetsImagesGen {
class $AssetsLogoGen {
const $AssetsLogoGen();
/// File path: assets/logo/ic_launcher.png
AssetGenImage get icLauncher =>
const AssetGenImage('assets/logo/ic_launcher.png');
/// File path: assets/logo/logo.jpeg
AssetGenImage get logo => const AssetGenImage('assets/logo/logo.jpeg');
/// File path: assets/logo/logo.png
AssetGenImage get logo => const AssetGenImage('assets/logo/logo.png');
/// File path: assets/logo/logo2.png
AssetGenImage get logo2 => const AssetGenImage('assets/logo/logo2.png');
@@ -330,17 +330,8 @@ class $AssetsLogoGen {
AssetGenImage get mylogo => const AssetGenImage('assets/logo/mylogo.png');
/// List of all assets
List<AssetGenImage> get values => [
icLauncher,
logo,
logo2,
logo3,
logo4,
logo5,
logo6,
logoAppIcon,
mylogo
];
List<AssetGenImage> get values =>
[logo, logo2, logo3, logo4, logo5, logo6, logoAppIcon, mylogo];
}
class Assets {
+66 -6
View File
@@ -1,19 +1,28 @@
import 'dart:developer';
import 'dart:typed_data';
import 'package:barcode_image/barcode_image.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/outlet_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/data/type/bussines_type.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:flutter/material.dart';
import 'package:barcode/barcode.dart';
import 'package:image/image.dart' as img;
Future<void> onPrint(
BuildContext context, {
required List<ProductQuantity> productQuantity,
required Order order,
}) async {
final outlet = await OutletLocalDatasource().get();
if (outlet.businessType == BusinessType.restaurant) {
final checkerPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('checker');
final kitchenPrinter =
@@ -21,6 +30,8 @@ Future<void> onPrint(
final barPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('bar');
final authData = await AuthLocalDataSource().getAuthData();
// Checker printer
if (checkerPrinter != null) {
try {
@@ -28,7 +39,7 @@ Future<void> onPrint(
productQuantity,
order.tableNumber ?? "",
order.orderNumber ?? "",
'kasir',
authData.user?.name ?? "",
checkerPrinter.paper.toIntegerFromText,
order.orderType ?? "",
);
@@ -51,7 +62,7 @@ Future<void> onPrint(
productQuantity,
order.tableNumber!,
order.orderNumber ?? "",
'kasir',
authData.user?.name ?? "",
kitchenPrinter.paper.toIntegerFromText,
order.orderType ?? "",
);
@@ -73,12 +84,13 @@ Future<void> onPrint(
productQuantity,
order.tableNumber ?? "",
order.orderNumber ?? "",
'kasir',
authData.user?.name ?? "",
barPrinter.paper.toIntegerFromText,
order.orderType ?? "",
);
await PrinterService().printWithPrinter(barPrinter, printValue, context);
await PrinterService()
.printWithPrinter(barPrinter, printValue, context);
} catch (e) {
log("Error printing bar order: $e");
ScaffoldMessenger.of(context).showSnackBar(
@@ -88,17 +100,45 @@ Future<void> onPrint(
}
}
if (outlet.businessType == BusinessType.ticketing) {
final ticketPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('ticket');
final barcode = await generateBarcodeAsUint8List(order.orderNumber ?? "");
if (ticketPrinter != null) {
try {
final printValue = await PrintDataoutputs.instance.printTicket(
order.totalAmount ?? 0,
barcode,
ticketPrinter.paper.toIntegerFromText,
);
await PrinterService()
// ignore: use_build_context_synchronously
.printWithPrinter(ticketPrinter, printValue, context);
} catch (e) {
log("Error printing ticket: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing ticket: $e')),
);
}
}
}
}
Future<void> onPrintRecipt(
context, {
required Order order,
required String paymentMethod,
required int nominalBayar,
required int kembalian,
required int taxPercentage,
}) async {
final receiptPrinter =
await ProductLocalDatasource.instance.getPrinterByCode('receipt');
final authData = await AuthLocalDataSource().getAuthData();
final settings = await SettingsLocalDatasource().getTax();
final outlet = await OutletLocalDatasource().get();
if (receiptPrinter != null) {
try {
@@ -108,8 +148,10 @@ Future<void> onPrintRecipt(
paymentMethod,
nominalBayar,
kembalian,
taxPercentage,
settings.value,
receiptPrinter.paper.toIntegerFromText,
order.orderType ?? "",
outlet,
);
await PrinterService()
.printWithPrinter(receiptPrinter, printValue, context);
@@ -121,3 +163,21 @@ Future<void> onPrintRecipt(
}
}
}
Future<Uint8List> generateBarcodeAsUint8List(String data) async {
// 1. Buat barcode instance (code128, qrCode, dll)
final barcode = Barcode.code128();
// 2. Siapkan canvas image dari package `image`
final image = img.Image(width: 600, height: 200);
// 3. Gambar barcode ke canvas menggunakan barcode_image
drawBarcode(
image,
barcode,
data,
);
// 4. Encode image ke Uint8List PNG
return Uint8List.fromList(img.encodePng(image));
}
+160 -1
View File
@@ -1,6 +1,9 @@
// lib/core/network/dio_client.dart
import 'package:awesome_dio_interceptor/awesome_dio_interceptor.dart';
import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/auth/login_page.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DioClient {
static final Dio _dio = Dio(BaseOptions(
@@ -10,6 +13,7 @@ class DioClient {
'Accept': 'application/json',
},
))
..interceptors.add(AuthInterceptor())
..interceptors.add(
AwesomeDioInterceptor(
logRequestTimeout: true,
@@ -20,3 +24,158 @@ class DioClient {
static Dio get instance => _dio;
}
class AuthInterceptor extends Interceptor {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
// Add token to request headers
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
// Check if error is 401 (Unauthorized) - token expired
if (err.response?.statusCode == 401) {
await _handleTokenExpired();
}
handler.next(err);
}
Future<void> _handleTokenExpired() async {
// Clear stored token
final prefs = await SharedPreferences.getInstance();
await prefs.remove('auth_token');
await prefs.remove('refresh_token');
await prefs.clear(); // Optional: clear all user data
// Navigate to login page
final context = navigatorKey.currentContext;
if (context != null) {
// Option 1: Navigate and remove all previous routes
context.pushReplacement(LoginPage());
// Option 2: If using GoRouter, uncomment below:
// GoRouter.of(context).go('/login');
// Option 3: If using custom routing, uncomment below:
// Navigator.of(context).pushAndRemoveUntil(
// MaterialPageRoute(builder: (context) => LoginPage()),
// (route) => false,
// );
}
}
}
// Alternative: Dengan Refresh Token Logic
class AuthInterceptorWithRefresh extends Interceptor {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
// Try refresh token first
final success = await _tryRefreshToken();
if (success) {
// Retry the original request
final response = await _retryRequest(err.requestOptions);
handler.resolve(response);
return;
} else {
// Refresh failed, redirect to login
await _handleTokenExpired();
}
}
handler.next(err);
}
Future<bool> _tryRefreshToken() async {
try {
final prefs = await SharedPreferences.getInstance();
final refreshToken = prefs.getString('refresh_token');
if (refreshToken == null) return false;
final response = await Dio().post(
'YOUR_REFRESH_TOKEN_ENDPOINT',
data: {'refresh_token': refreshToken},
);
if (response.statusCode == 200) {
final newToken = response.data['access_token'];
final newRefreshToken = response.data['refresh_token'];
await prefs.setString('auth_token', newToken);
await prefs.setString('refresh_token', newRefreshToken);
return true;
}
} catch (e) {
print('Refresh token failed: $e');
}
return false;
}
Future<Response> _retryRequest(RequestOptions options) async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('auth_token');
options.headers['Authorization'] = 'Bearer $token';
return await DioClient.instance.request(
options.path,
options: Options(
method: options.method,
headers: options.headers,
),
data: options.data,
queryParameters: options.queryParameters,
);
}
Future<void> _handleTokenExpired() async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
final context = navigatorKey.currentContext;
if (context != null) {
Navigator.of(context).pushNamedAndRemoveUntil(
'/login',
(route) => false,
);
}
}
}
+45 -5
View File
@@ -1,6 +1,7 @@
import 'dart:math';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart';
import 'package:flutter/services.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
@@ -748,6 +749,8 @@ class PrintDataoutputs {
int kembalian,
int taxPercentage,
int paper,
String orderType,
Outlet outlet,
) async {
List<int> bytes = [];
@@ -757,7 +760,7 @@ class PrintDataoutputs {
bytes += generator.reset();
bytes += generator.text('Guapatlu Khas Bakmi Jambi',
bytes += generator.text(outlet.name ?? "",
styles: const PosStyles(
bold: true,
align: PosAlign.center,
@@ -765,9 +768,9 @@ class PrintDataoutputs {
width: PosTextSize.size1,
));
bytes += generator.text('Gading Boulevard w2, No 25, Jakarta',
bytes += generator.text(outlet.address ?? "",
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.text('085-77777-3839',
bytes += generator.text(outlet.phoneNumber ?? "",
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.text(
@@ -855,14 +858,16 @@ class PrintDataoutputs {
? '------------------------------------------------'
: '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.text('Dine In',
bytes += generator.text(orderType,
styles: const PosStyles(bold: true, align: PosAlign.center));
bytes += generator.text(
paper == 80
? '------------------------------------------------'
: '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center));
for (final product in (order.orderItems ?? <OrderItem>[])) {
for (final product
in (order.orderItems?.where((item) => item.status != 'cancelled') ??
<OrderItem>[])) {
bytes += generator.row([
PosColumn(
text: '${product.quantity} x ${product.productName}',
@@ -1398,4 +1403,39 @@ class PrintDataoutputs {
return bytes;
}
Future<List<int>> printTicket(
int totalPrice, Uint8List imageQris, int paper) async {
List<int> bytes = [];
final profile = await CapabilityProfile.load();
final generator =
Generator(paper == 58 ? PaperSize.mm58 : PaperSize.mm80, profile);
final img.Image? orginalImage = img.decodeImage(imageQris);
bytes += generator.reset();
// final Uint8List bytesData = data.buffer.asUint8List();
// final img.Image? orginalImage = img.decodeImage(bytesData);
// bytes += generator.reset();
bytes += generator.text('Scan Ticket',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.feed(2);
if (orginalImage != null) {
final img.Image grayscalledImage = img.grayscale(orginalImage);
final img.Image resizedImage =
img.copyResize(grayscalledImage, width: 240);
bytes += generator.imageRaster(resizedImage, align: PosAlign.center);
bytes += generator.feed(1);
}
bytes += generator.text('Price : ${totalPrice.currencyFormatRp}',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.feed(4);
bytes += generator.cut();
return bytes;
}
}
@@ -272,18 +272,26 @@ class OrderRemoteDatasource {
String status = 'completed',
required DateTime dateFrom,
required DateTime dateTo,
String? search,
}) async {
try {
final authData = await AuthLocalDataSource().getAuthData();
final response = await dio.get(
'${Variables.baseUrl}/api/v1/orders',
queryParameters: {
Map<String, dynamic> params = {
'page': page,
'limit': limit,
'status': status,
'date_from': DateFormat('dd-MM-yyyy').format(dateFrom),
'date_to': DateFormat('dd-MM-yyyy').format(dateTo),
},
};
if (search != null && search.isNotEmpty) {
params['search'] = search;
}
final response = await dio.get(
'${Variables.baseUrl}/api/v1/orders',
queryParameters: params,
options: Options(
headers: {
'Authorization': 'Bearer ${authData.token}',
@@ -397,7 +405,7 @@ class OrderRemoteDatasource {
}
}
Future<Either<String, OrderDetailResponseModel>> addToOrder({
Future<Either<String, bool>> addToOrder({
required String orderId,
required List<OrderItemRequest> orderItems,
}) async {
@@ -421,8 +429,7 @@ class OrderRemoteDatasource {
);
if (response.statusCode == 200) {
final data = OrderDetailResponseModel.fromMap(response.data);
return Right(data);
return Right(true);
} else {
return const Left('Gagal menambahkan pesanan pesanan');
}
@@ -434,7 +441,7 @@ class OrderRemoteDatasource {
return Left(errorMessage);
} catch (e) {
log("đź’Ą Unexpected error: $e");
return const Left('Terjadi kesalahan tak terduga');
return const Left('Terjadi kesalahan, coba lagi nanti.');
}
}
@@ -579,4 +586,39 @@ class OrderRemoteDatasource {
return const Left('Terjadi kesalahan tak terduga');
}
}
Future<Either<String, PaymentSuccessResponseModel>> createPaymentSplitBill(
PaymentSplitBillRequest request) async {
final authData = await AuthLocalDataSource().getAuthData();
final url = '${Variables.baseUrl}/api/v1/orders/split-bill';
try {
final response = await dio.post(
url,
data: request.toMap(),
options: Options(
headers: {
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
'Content-Type': 'application/json',
},
),
);
if (response.statusCode == 200) {
return Right(PaymentSuccessResponseModel.fromMap(response.data));
} else {
return const Left('Gagal membuat pembayaran');
}
} on DioException catch (e) {
final errorMessage =
e.response?.data['message'] ?? 'Terjadi kesalahan, coba lagi nanti.';
log("đź’Ą Dio error: ${e.message}");
log("đź’Ą Dio response: ${e.response?.data}");
return Left(errorMessage);
} catch (e) {
log("đź’Ą Unexpected error: $e");
return const Left('Terjadi kesalahan tak terduga');
}
}
}
@@ -0,0 +1,30 @@
import 'dart:developer';
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
class OutletLocalDatasource {
Future<void> save(Outlet outlet) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('outlet', outlet.toJson());
log('Outlet Local Data: ${outlet.toJson()}');
} catch (e) {
log('Error saving outlet: $e');
}
}
Future<void> remove() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('outlet');
}
Future<Outlet> get() async {
final prefs = await SharedPreferences.getInstance();
final outlet = prefs.getString('outlet');
log('Outlet Local Data: $outlet');
return Outlet.fromJson(outlet!);
}
}
@@ -2,6 +2,7 @@ import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/datasources/outlet_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
import 'package:enaklo_pos/presentation/home/models/outlet_model.dart';
import 'package:enaklo_pos/presentation/setting/models/tax_model.dart';
@@ -78,6 +79,8 @@ class OutletRemoteDataSource {
value: data.data?.taxRate ?? 0,
),
);
await OutletLocalDatasource().save(data.data!);
return Right(data);
} else {
return const Left('Failed to get outlets');
@@ -55,7 +55,7 @@ class TableRemoteDataSource {
Future<Either<String, TableResponseModel>> getTable({
int page = 1,
int limit = 10,
int limit = 50,
String? status,
}) async {
try {
@@ -131,4 +131,40 @@ class TableRemoteDataSource {
return const Left('Unexpected error occurred');
}
}
Future<Either<String, bool>> transferTable({
required String fromTableId,
required String toTableId,
}) async {
try {
final authData = await AuthLocalDataSource().getAuthData();
final url = '${Variables.baseUrl}/api/v1/tables/transfer';
final response = await dio.put(
url,
data: {
"from_table": fromTableId,
"to_table": toTableId,
},
options: Options(
headers: {
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
},
),
);
if (response.statusCode == 200 || response.statusCode == 201) {
return Right(true);
} else {
return const Left('Failed to create table');
}
} on DioException catch (e) {
log("Dio error: ${e.message}");
return Left(e.response?.data['message'] ?? 'Gagal membuat table');
} catch (e) {
log("Unexpected error: $e");
return const Left('Unexpected error occurred');
}
}
}
@@ -78,3 +78,60 @@ class PaymentOrderItemModel {
"amount": amount,
};
}
class PaymentSplitBillRequest {
final String orderId;
final String paymentMethodId;
final String customerId;
final String type; // e.g., "AMOUNT" or "ITEM"
final int amount;
final List<SplitItem> items;
PaymentSplitBillRequest({
required this.orderId,
required this.paymentMethodId,
required this.customerId,
required this.type,
required this.amount,
required this.items,
});
Map<String, dynamic> toMap() {
Map<String, dynamic> data = {
'order_id': orderId,
'payment_method_id': paymentMethodId,
'type': type,
};
if (customerId.isNotEmpty) {
data['customer_id'] = customerId;
}
if (type == "AMOUNT") {
data['amount'] = amount;
}
if (type == "ITEM") {
data['items'] = items.map((e) => e.toJson()).toList();
}
return data;
}
}
class SplitItem {
final String orderItemId;
final int quantity;
SplitItem({
required this.orderItemId,
required this.quantity,
});
Map<String, dynamic> toJson() {
return {
'order_item_id': orderItemId,
'quantity': quantity,
};
}
}
@@ -59,11 +59,17 @@ class DashboardAnalyticData {
dateFrom: map['date_from'],
dateTo: map['date_to'],
overview: DashboardOverview.fromMap(map['overview']),
topProducts: List<TopProduct>.from(
topProducts: map['top_products'] == null
? []
: List<TopProduct>.from(
map['top_products']?.map((x) => TopProduct.fromMap(x))),
paymentMethods: List<PaymentMethodAnalytic>.from(map['payment_methods']
paymentMethods: map['payment_methods'] == null
? []
: List<PaymentMethodAnalytic>.from(map['payment_methods']
?.map((x) => PaymentMethodAnalytic.fromMap(x))),
recentSales: List<RecentSale>.from(
recentSales: map['recent_sales'] == null
? []
: List<RecentSale>.from(
map['recent_sales']?.map((x) => RecentSale.fromMap(x))),
);
+268 -140
View File
@@ -1,35 +1,5 @@
import 'dart:convert';
class OrderResponseModel {
final bool? success;
final OrderData? data;
final dynamic errors;
OrderResponseModel({
this.success,
this.data,
this.errors,
});
factory OrderResponseModel.fromJson(String str) =>
OrderResponseModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory OrderResponseModel.fromMap(Map<String, dynamic> json) =>
OrderResponseModel(
success: json["success"],
data: json["data"] == null ? null : OrderData.fromMap(json["data"]),
errors: json["errors"],
);
Map<String, dynamic> toMap() => {
"success": success,
"data": data?.toMap(),
"errors": errors,
};
}
class OrderDetailResponseModel {
final bool? success;
final Order? data;
@@ -60,41 +30,78 @@ class OrderDetailResponseModel {
};
}
class OrderResponseModel {
final bool? success;
final OrderData? data;
OrderResponseModel({
this.success,
this.data,
});
factory OrderResponseModel.fromJson(String str) =>
OrderResponseModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap());
factory OrderResponseModel.fromMap(Map<String, dynamic> map) {
return OrderResponseModel(
success: map['success'] ?? false,
data: OrderData.fromMap(map['data']),
);
}
Map<String, dynamic> toMap() {
return {
'success': success,
'data': data?.toMap(),
};
}
}
class OrderData {
final List<Order>? orders;
final List<Payment>? payments;
final int? totalCount;
final int? page;
final int? limit;
final int? totalPages;
OrderData({
this.orders,
this.totalCount,
this.page,
this.limit,
this.totalPages,
required this.orders,
required this.payments,
required this.totalCount,
required this.page,
required this.limit,
required this.totalPages,
});
factory OrderData.fromMap(Map<String, dynamic> json) => OrderData(
orders: json["orders"] == null
factory OrderData.fromMap(Map<String, dynamic> map) {
return OrderData(
orders: map["orders"] == null
? []
: List<Order>.from(json["orders"].map((x) => Order.fromMap(x))),
totalCount: json["total_count"],
page: json["page"],
limit: json["limit"],
totalPages: json["total_pages"],
: List<Order>.from(map['orders']?.map((x) => Order.fromMap(x))),
payments: map["payments"] == null
? []
: List<Payment>.from(map['payments']?.map((x) => Payment.fromMap(x))),
totalCount: map['total_count'],
page: map['page'],
limit: map['limit'],
totalPages: map['total_pages'],
);
}
Map<String, dynamic> toMap() => {
"orders": orders == null
? []
: List<dynamic>.from(orders!.map((x) => x.toMap())),
"total_count": totalCount,
"page": page,
"limit": limit,
"total_pages": totalPages,
Map<String, dynamic> toMap() {
return {
'orders': orders?.map((x) => x.toMap()).toList(),
'payments': payments?.map((x) => x.toMap()).toList(),
'total_count': totalCount,
'page': page,
'limit': limit,
'total_pages': totalPages,
};
}
}
class Order {
final String? id;
@@ -108,11 +115,21 @@ class Order {
final int? taxAmount;
final int? discountAmount;
final int? totalAmount;
final int? totalCost;
final int? remainingAmount;
final String? paymentStatus;
final int? refundAmount;
final bool? isVoid;
final bool? isRefund;
final String? notes;
final Map<String, dynamic>? metadata;
final DateTime? createdAt;
final DateTime? updatedAt;
final List<OrderItem>? orderItems;
final List<Payment>? payments;
final int? totalPaid;
final int? paymentCount;
final String? splitType;
Order({
this.id,
@@ -126,76 +143,108 @@ class Order {
this.taxAmount,
this.discountAmount,
this.totalAmount,
this.totalCost,
this.remainingAmount,
this.paymentStatus,
this.refundAmount,
this.isVoid,
this.isRefund,
this.notes,
this.metadata,
this.createdAt,
this.updatedAt,
this.orderItems,
this.payments,
this.totalPaid,
this.paymentCount,
this.splitType,
});
factory Order.fromMap(Map<String, dynamic> json) => Order(
id: json["id"],
orderNumber: json["order_number"],
outletId: json["outlet_id"],
userId: json["user_id"],
tableNumber: json["table_number"],
orderType: json["order_type"],
status: json["status"],
subtotal: json["subtotal"],
taxAmount: json["tax_amount"],
discountAmount: json["discount_amount"],
totalAmount: json["total_amount"],
notes: json["notes"],
metadata: json["metadata"] ?? {},
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
orderItems: json["order_items"] == null
factory Order.fromMap(Map<String, dynamic> map) {
return Order(
id: map['id'],
orderNumber: map['order_number'],
outletId: map['outlet_id'],
userId: map['user_id'],
tableNumber: map['table_number'],
orderType: map['order_type'],
status: map['status'],
subtotal: map['subtotal'],
taxAmount: map['tax_amount'],
discountAmount: map['discount_amount'],
totalAmount: map['total_amount'],
totalCost: map['total_cost'],
remainingAmount: map['remaining_amount'],
paymentStatus: map['payment_status'],
refundAmount: map['refund_amount'],
isVoid: map['is_void'],
isRefund: map['is_refund'],
notes: map['notes'],
metadata: map['metadata'] ?? {},
createdAt: DateTime.parse(map['created_at']),
updatedAt: DateTime.parse(map['updated_at']),
orderItems: map["order_items"] == null
? []
: List<OrderItem>.from(
json["order_items"].map((x) => OrderItem.fromMap(x))),
);
Map<String, dynamic> toMap() => {
"id": id,
"order_number": orderNumber,
"outlet_id": outletId,
"user_id": userId,
"table_number": tableNumber,
"order_type": orderType,
"status": status,
"subtotal": subtotal,
"tax_amount": taxAmount,
"discount_amount": discountAmount,
"total_amount": totalAmount,
"notes": notes,
"metadata": metadata,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
"order_items": orderItems == null
map['order_items'].map((x) => OrderItem.fromMap(x))),
payments: map["payments"] == null
? []
: List<dynamic>.from(orderItems!.map((x) => x.toMap())),
: List<Payment>.from(map['payments'].map((x) => Payment.fromMap(x))),
totalPaid: map['total_paid'],
paymentCount: map['payment_count'],
splitType: map['split_type'] ?? "",
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'order_number': orderNumber,
'outlet_id': outletId,
'user_id': userId,
'table_number': tableNumber,
'order_type': orderType,
'status': status,
'subtotal': subtotal,
'tax_amount': taxAmount,
'discount_amount': discountAmount,
'total_amount': totalAmount,
'total_cost': totalCost,
'remaining_amount': remainingAmount,
'payment_status': paymentStatus,
'refund_amount': refundAmount,
'is_void': isVoid,
'is_refund': isRefund,
'notes': notes,
'metadata': metadata,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
'order_items': orderItems?.map((x) => x.toMap()).toList(),
'payments': payments?.map((x) => x.toMap()).toList(),
'total_paid': totalPaid,
'payment_count': paymentCount,
'split_type': splitType,
};
}
}
class OrderItem {
final String? id;
final String? orderId;
final String? productId;
final String? productName;
final String? productVariantId;
final String? productVariantName;
final int? quantity;
final int? unitPrice;
final int? totalPrice;
final List<dynamic>? modifiers;
final String? notes;
final String? status;
final DateTime? createdAt;
final DateTime? updatedAt;
String? id;
String? orderId;
String? productId;
String? productName;
String? productVariantId;
String? productVariantName;
int? quantity;
int? unitPrice;
int? totalPrice;
List<dynamic>? modifiers;
String? notes;
String? status;
DateTime? createdAt;
DateTime? updatedAt;
String? printerType;
int? paidQuantity;
OrderItem({
this.id,
@@ -212,43 +261,122 @@ class OrderItem {
this.status,
this.createdAt,
this.updatedAt,
this.printerType,
this.paidQuantity,
});
factory OrderItem.fromMap(Map<String, dynamic> json) => OrderItem(
id: json["id"],
orderId: json["order_id"],
productId: json["product_id"],
productName: json["product_name"],
productVariantId: json["product_variant_id"],
productVariantName: json["product_variant_name"],
quantity: json["quantity"],
unitPrice: json["unit_price"],
totalPrice: json["total_price"],
modifiers: json["modifiers"] ?? [],
notes: json["notes"],
status: json["status"],
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
factory OrderItem.fromMap(Map<String, dynamic> map) {
return OrderItem(
id: map['id'],
orderId: map['order_id'],
productId: map['product_id'],
productName: map['product_name'],
productVariantId: map['product_variant_id'],
productVariantName: map['product_variant_name'],
quantity: map['quantity'],
unitPrice: map['unit_price'],
totalPrice: map['total_price'],
modifiers:
map['modifiers'] == null ? [] : List<dynamic>.from(map['modifiers']),
notes: map['notes'],
status: map['status'],
createdAt: DateTime.parse(map['created_at']),
updatedAt: DateTime.parse(map['updated_at']),
printerType: map['printer_type'],
paidQuantity: map['paid_quantity'],
);
}
Map<String, dynamic> toMap() => {
"id": id,
"order_id": orderId,
"product_id": productId,
"product_name": productName,
"product_variant_id": productVariantId,
"product_variant_name": productVariantName,
"quantity": quantity,
"unit_price": unitPrice,
"total_price": totalPrice,
"modifiers": modifiers,
"notes": notes,
"status": status,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
Map<String, dynamic> toMap() {
return {
'id': id,
'order_id': orderId,
'product_id': productId,
'product_name': productName,
'product_variant_id': productVariantId,
'product_variant_name': productVariantName,
'quantity': quantity,
'unit_price': unitPrice,
'total_price': totalPrice,
'modifiers': modifiers,
'notes': notes,
'status': status,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
'printer_type': printerType,
'paid_quantity': paidQuantity,
};
}
}
class Payment {
final String? id;
final String? orderId;
final String? paymentMethodId;
final String? paymentMethodName;
final String? paymentMethodType;
final int? amount;
final String? status;
final int? splitNumber;
final int? splitTotal;
final String? splitDescription;
final int? refundAmount;
final Map<String, dynamic>? metadata;
final DateTime? createdAt;
final DateTime? updatedAt;
Payment({
this.id,
this.orderId,
this.paymentMethodId,
this.paymentMethodName,
this.paymentMethodType,
this.amount,
this.status,
this.splitNumber,
this.splitTotal,
this.splitDescription,
this.refundAmount,
this.metadata,
this.createdAt,
this.updatedAt,
});
factory Payment.fromMap(Map<String, dynamic> map) {
return Payment(
id: map['id'],
orderId: map['order_id'],
paymentMethodId: map['payment_method_id'],
paymentMethodName: map['payment_method_name'],
paymentMethodType: map['payment_method_type'],
amount: map['amount'],
status: map['status'],
splitNumber: map['split_number'],
splitTotal: map['split_total'],
splitDescription: map['split_description'],
refundAmount: map['refund_amount'],
metadata: map['metadata'] ?? {},
createdAt: DateTime.parse(map['created_at']),
updatedAt: DateTime.parse(map['updated_at']),
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'order_id': orderId,
'payment_method_id': paymentMethodId,
'payment_method_name': paymentMethodName,
'payment_method_type': paymentMethodType,
'amount': amount,
'status': status,
'split_number': splitNumber,
'split_total': splitTotal,
'split_description': splitDescription,
'refund_amount': refundAmount,
'metadata': metadata,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
};
}
}
@@ -64,8 +64,11 @@ class PaymentMethodAnalyticData {
dateTo: DateTime.parse(map['date_to']),
groupBy: map['group_by'],
summary: PaymentSummary.fromMap(map['summary']),
data: List<PaymentMethodAnalyticItem>.from(
map['data']?.map((x) => PaymentMethodAnalyticItem.fromMap(x)) ?? [],
data: map['data'] == null
? []
: List<PaymentMethodAnalyticItem>.from(
map['data']?.map((x) => PaymentMethodAnalyticItem.fromMap(x)) ??
[],
),
);
}
@@ -52,7 +52,9 @@ class ProductAnalyticData {
outletId: map['outlet_id'],
dateFrom: DateTime.parse(map['date_from']),
dateTo: DateTime.parse(map['date_to']),
data: List<ProductAnalyticItem>.from(
data: map['data'] == null
? []
: List<ProductAnalyticItem>.from(
map['data'].map((x) => ProductAnalyticItem.fromMap(x)),
),
);
@@ -72,9 +72,13 @@ class ProfitLossData {
dateTo: map['date_to'],
groupBy: map['group_by'],
summary: ProfitLossSummary.fromMap(map['summary']),
data: List<ProfitLossItem>.from(
data: map['data'] == null
? []
: List<ProfitLossItem>.from(
map['data'].map((x) => ProfitLossItem.fromMap(x))),
productData: List<ProfitLossProduct>.from(
productData: map['product_data'] == null
? []
: List<ProfitLossProduct>.from(
map['product_data'].map((x) => ProfitLossProduct.fromMap(x))),
);
}
+17
View File
@@ -0,0 +1,17 @@
enum BusinessType {
restaurant('restaurant'),
retail('retail'),
service('service'),
ticketing('ticketing'),
entertainment('entertainment');
final String value;
const BusinessType(this.value);
static BusinessType fromString(String value) {
return BusinessType.values.firstWhere(
(type) => type.value == value,
orElse: () => BusinessType.restaurant,
);
}
}
+10
View File
@@ -1,5 +1,6 @@
import 'dart:developer';
import 'package:enaklo_pos/core/constants/theme.dart';
import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/datasources/analytic_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/customer_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/file_remote_datasource.dart';
@@ -18,7 +19,9 @@ import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/upload_file/upload_file_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart';
import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart';
import 'package:flutter/material.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
@@ -279,8 +282,15 @@ class _MyAppState extends State<MyApp> {
BlocProvider(
create: (context) => CategoryLoaderBloc(CategoryRemoteDatasource()),
),
BlocProvider(
create: (context) => GetPrinterTicketBloc(),
),
BlocProvider(
create: (context) => TransferTableBloc(TableRemoteDataSource()),
),
],
child: MaterialApp(
navigatorKey: AuthInterceptor.navigatorKey,
debugShowCheckedModeBanner: false,
title: 'POS Resto App',
theme: getApplicationTheme,
@@ -30,11 +30,6 @@ class CustomerTitle extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: () => context.pop(),
icon: Icon(Icons.arrow_back, color: AppColors.black),
),
SpaceWidth(16),
Expanded(
child: Text(
title,
@@ -77,8 +77,8 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
customerName: event.customerName,
notes: '',
orderType: event.orderType.name,
tableId: event.table.id,
tableNumber: event.table.tableName,
tableId: event.table?.id ?? "",
tableNumber: event.table?.tableName ?? "",
outletId: userData.user?.outletId,
customerId: event.customer?.id ?? '',
orderItems: event.items
@@ -131,11 +131,11 @@ class OrderFormBloc extends Bloc<OrderFormEvent, OrderFormState> {
result.fold(
(error) => emit(_Error(error)),
(success) => emit(_Success(success.data!)),
(success) => emit(_SuccessMsg()),
);
} catch (e) {
log("Error in AddOrderItemsBloc: $e");
emit(_Error("Failed to add order items: $e"));
emit(_Error("Ada kesalahan. Coba lagi nanti"));
}
},
);
@@ -27,7 +27,7 @@ mixin _$OrderFormEvent {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -53,7 +53,7 @@ mixin _$OrderFormEvent {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -76,7 +76,7 @@ mixin _$OrderFormEvent {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -226,7 +226,7 @@ class _$StartedImpl implements _Started {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -255,7 +255,7 @@ class _$StartedImpl implements _Started {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -281,7 +281,7 @@ class _$StartedImpl implements _Started {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -496,7 +496,7 @@ class _$CreateImpl implements _Create {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -525,7 +525,7 @@ class _$CreateImpl implements _Create {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -551,7 +551,7 @@ class _$CreateImpl implements _Create {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -652,7 +652,7 @@ abstract class _$$CreateWithPaymentMethodImplCopyWith<$Res> {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod});
}
@@ -674,7 +674,7 @@ class __$$CreateWithPaymentMethodImplCopyWithImpl<$Res>
Object? customerName = null,
Object? customer = freezed,
Object? orderType = null,
Object? table = null,
Object? table = freezed,
Object? paymentMethod = null,
}) {
return _then(_$CreateWithPaymentMethodImpl(
@@ -694,10 +694,10 @@ class __$$CreateWithPaymentMethodImplCopyWithImpl<$Res>
? _value.orderType
: orderType // ignore: cast_nullable_to_non_nullable
as OrderType,
table: null == table
table: freezed == table
? _value.table
: table // ignore: cast_nullable_to_non_nullable
as TableModel,
as TableModel?,
paymentMethod: null == paymentMethod
? _value.paymentMethod
: paymentMethod // ignore: cast_nullable_to_non_nullable
@@ -733,7 +733,7 @@ class _$CreateWithPaymentMethodImpl implements _CreateWithPaymentMethod {
@override
final OrderType orderType;
@override
final TableModel table;
final TableModel? table;
@override
final PaymentMethod paymentMethod;
@@ -790,7 +790,7 @@ class _$CreateWithPaymentMethodImpl implements _CreateWithPaymentMethod {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -820,7 +820,7 @@ class _$CreateWithPaymentMethodImpl implements _CreateWithPaymentMethod {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -847,7 +847,7 @@ class _$CreateWithPaymentMethodImpl implements _CreateWithPaymentMethod {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -922,7 +922,7 @@ abstract class _CreateWithPaymentMethod implements OrderFormEvent {
required final String customerName,
required final Customer? customer,
required final OrderType orderType,
required final TableModel table,
required final TableModel? table,
required final PaymentMethod paymentMethod}) =
_$CreateWithPaymentMethodImpl;
@@ -930,7 +930,7 @@ abstract class _CreateWithPaymentMethod implements OrderFormEvent {
String get customerName;
Customer? get customer;
OrderType get orderType;
TableModel get table;
TableModel? get table;
PaymentMethod get paymentMethod;
/// Create a copy of OrderFormEvent
@@ -1034,7 +1034,7 @@ class _$AddToOrderImpl implements _AddToOrder {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -1063,7 +1063,7 @@ class _$AddToOrderImpl implements _AddToOrder {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1089,7 +1089,7 @@ class _$AddToOrderImpl implements _AddToOrder {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1276,7 +1276,7 @@ class _$RefundImpl implements _Refund {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -1305,7 +1305,7 @@ class _$RefundImpl implements _Refund {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1331,7 +1331,7 @@ class _$RefundImpl implements _Refund {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1520,7 +1520,7 @@ class _$VoidOrderImpl implements _VoidOrder {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -1549,7 +1549,7 @@ class _$VoidOrderImpl implements _VoidOrder {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1575,7 +1575,7 @@ class _$VoidOrderImpl implements _VoidOrder {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1737,7 +1737,7 @@ class _$ToggleItemImpl implements _ToggleItem {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -1766,7 +1766,7 @@ class _$ToggleItemImpl implements _ToggleItem {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1792,7 +1792,7 @@ class _$ToggleItemImpl implements _ToggleItem {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -1951,7 +1951,7 @@ class _$ToggleSelectAllImpl implements _ToggleSelectAll {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)
createWithPayment,
required TResult Function(List<ProductQuantity> items, String orderId)
@@ -1980,7 +1980,7 @@ class _$ToggleSelectAllImpl implements _ToggleSelectAll {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult? Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -2006,7 +2006,7 @@ class _$ToggleSelectAllImpl implements _ToggleSelectAll {
String customerName,
Customer? customer,
OrderType orderType,
TableModel table,
TableModel? table,
PaymentMethod paymentMethod)?
createWithPayment,
TResult Function(List<ProductQuantity> items, String orderId)? addToOrder,
@@ -15,7 +15,7 @@ class OrderFormEvent with _$OrderFormEvent {
required String customerName,
required Customer? customer,
required OrderType orderType,
required TableModel table,
required TableModel? table,
required PaymentMethod paymentMethod,
}) = _CreateWithPaymentMethod;
const factory OrderFormEvent.addToOrder({
@@ -289,7 +289,7 @@ class _PaymentAddOrderDialogState extends State<PaymentAddOrderDialog> {
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: (data) {
successMsg: () {
context.pop();
context.pushReplacement(
SuccessSaveOrderPage(
@@ -45,7 +45,6 @@ class OrderRequestModel {
Map<String, dynamic> data = {
"outlet_id": outletId,
"table_number": tableNumber,
"table_id": tableId,
"order_type": orderType,
"notes": notes,
"order_items": orderItems == null
@@ -58,6 +57,10 @@ class OrderRequestModel {
data["customer_id"] = customerId;
}
if (tableId != null && tableId != "") {
data["table_id"] = tableId;
}
return data;
}
}
@@ -1,5 +1,7 @@
import 'dart:convert';
import 'package:enaklo_pos/data/type/bussines_type.dart';
class OutletResponse {
final bool? success;
final OutletData? data;
@@ -101,7 +103,7 @@ class Outlet {
final String? name;
final String? address;
final String? phoneNumber;
final String? businessType;
final BusinessType? businessType;
final String? currency;
final int? taxRate;
final bool? isActive;
@@ -128,7 +130,7 @@ class Outlet {
name: json["name"],
address: json["address"],
phoneNumber: json["phone_number"],
businessType: json["business_type"],
businessType: BusinessType.fromString(json["business_type"]),
currency: json["currency"],
taxRate: json["tax_rate"],
isActive: json["is_active"],
@@ -146,7 +148,7 @@ class Outlet {
"name": name,
"address": address,
"phone_number": phoneNumber,
"business_type": businessType,
"business_type": businessType?.value,
"currency": currency,
"tax_rate": taxRate,
"is_active": isActive,
@@ -355,7 +355,10 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
(previousValue, element) =>
previousValue +
(element.product.price! *
element.quantity),
element.quantity) +
(element.variant
?.priceModifier ??
0),
));
return Text(
price.currencyFormatRp,
@@ -500,7 +503,9 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
(previousValue, element) =>
previousValue +
(element.product.price! *
element.quantity),
element.quantity) +
(element.variant?.priceModifier ??
0),
),
);
@@ -1065,12 +1070,15 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
}
context.read<OrderFormBloc>().add(
OrderFormEvent.create(
OrderFormEvent
.createWithPayment(
items: items,
customerName:
customerController
.text,
orderType: orderType,
paymentMethod:
selectedPaymentMethod!,
table: widget.table,
customer:
selectedCustomer,
@@ -3,6 +3,8 @@ import 'dart:async';
import 'dart:developer';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:enaklo_pos/presentation/customer/pages/customer_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/setting_page.dart';
import 'package:enaklo_pos/presentation/table/pages/table_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -14,8 +16,6 @@ import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/presentation/auth/login_page.dart';
import 'package:enaklo_pos/presentation/report/pages/report_page.dart';
import 'package:enaklo_pos/presentation/setting/bloc/sync_order/sync_order_bloc.dart';
import 'package:enaklo_pos/presentation/setting/pages/printer_configuration_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/settings_page.dart';
import '../../../core/assets/assets.gen.dart';
import '../../auth/bloc/logout/logout_bloc.dart';
@@ -59,9 +59,8 @@ class _DashboardPageState extends State<DashboardPage> {
),
const TablePage(),
const ReportPage(),
const PrinterConfigurationPage(),
// SalesPage(),
const SettingsPage(),
const CustomerPage(),
const SettingPage(),
];
// ignore: unused_local_variable
_connectivitySubscription = Connectivity()
@@ -121,7 +120,7 @@ class _DashboardPageState extends State<DashboardPage> {
onTap: () => _onItemTapped(2),
),
NavItem(
iconPath: Assets.icons.print.path,
iconPath: Assets.icons.people.path,
isActive: _selectedIndex == 3,
onTap: () => _onItemTapped(3),
),
+4 -2
View File
@@ -429,8 +429,10 @@ class _HomePageState extends State<HomePage> {
}
return products
.map((e) =>
e.product.price! *
e.quantity)
(e.product.price! *
e.quantity) +
(e.variant?.priceModifier ??
0))
.reduce((value, element) =>
value + element);
});
@@ -3,7 +3,6 @@ import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/customer/pages/customer_page.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import 'package:enaklo_pos/presentation/home/dialog/delivery_dialog.dart';
import 'package:enaklo_pos/presentation/home/dialog/type_dialog.dart';
@@ -20,7 +19,7 @@ class HomeRightTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: context.deviceHeight * 0.15,
height: context.deviceHeight * 0.12,
decoration: BoxDecoration(
color: AppColors.primary,
border: Border(
@@ -33,7 +32,8 @@ class HomeRightTitle extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
Expanded(
child: Row(
children: [
Expanded(
child: Button.filled(
@@ -53,29 +53,11 @@ class HomeRightTitle extends StatelessWidget {
label: 'Daftar Pesanan',
),
),
Expanded(
child: Button.filled(
width: 180.0,
height: 40,
elevation: 0,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
icon: Icon(
Icons.person_outline,
color: Colors.white,
size: 24,
),
onPressed: () {
if (table == null) {
context.push(CustomerPage());
}
},
label: 'Pelanggan',
),
),
],
),
Row(
),
Expanded(
child: Row(
children: [
Expanded(
child: BlocBuilder<CheckoutBloc, CheckoutState>(
@@ -199,6 +181,7 @@ class HomeRightTitle extends StatelessWidget {
),
],
),
),
],
),
);
@@ -17,7 +17,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class PaymentPage extends StatefulWidget {
final Order order;
const PaymentPage({Key? key, required this.order}) : super(key: key);
final bool isSplit;
final String? splitType;
final String? customerId;
const PaymentPage({
super.key,
required this.order,
this.isSplit = false,
this.splitType,
this.customerId,
});
@override
State<PaymentPage> createState() => _PaymentPageState();
@@ -363,7 +372,8 @@ class _PaymentPageState extends State<PaymentPage> {
success: (data) {
context.pushReplacement(SuccessPaymentPage(
productQuantity: widget.order.orderItems
?.map(
?.where((item) => item.status == "pending")
.map(
(item) => ProductQuantity(
product: Product(
name: item.productName,
@@ -375,6 +385,8 @@ class _PaymentPageState extends State<PaymentPage> {
.toList() ??
[],
payment: data,
paymentMethod: selectedPaymentMethod?.name ?? '',
nominalBayar: totalPriceController.text.toIntegerFromText,
));
},
error: (message) {
@@ -432,7 +444,7 @@ class _PaymentPageState extends State<PaymentPage> {
final itemPending = widget.order.orderItems
?.where((item) => item.status == "pending")
.toList();
if (widget.isSplit == false) {
final request = PaymentRequestModel(
amount: widget.order.totalAmount ?? 0,
orderId: widget.order.id,
@@ -450,5 +462,24 @@ class _PaymentPageState extends State<PaymentPage> {
);
context.read<PaymentFormBloc>().add(PaymentFormEvent.create(request));
} else {
final request = PaymentSplitBillRequest(
amount: widget.order.totalAmount ?? 0,
customerId: widget.customerId ?? "",
items: itemPending
?.map((item) => SplitItem(
orderItemId: item.id ?? "",
quantity: item.quantity ?? 0,
))
.toList() ??
[],
orderId: widget.order.id ?? "",
paymentMethodId: selectedPaymentMethod?.id ?? "",
type: widget.splitType ?? "",
);
context.read<PaymentFormBloc>().add(
PaymentFormEvent.createSplitBill(request),
);
}
}
}
@@ -1,6 +1,8 @@
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/presentation/home/pages/dashboard_page.dart';
import 'package:flutter/material.dart';
class RefundSuccessDialog extends StatelessWidget {
@@ -102,8 +104,7 @@ class RefundSuccessDialog extends StatelessWidget {
Expanded(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
context.push(DashboardPage());
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
+30 -46
View File
@@ -39,7 +39,11 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
late Animation<double> _scaleAnimation;
final List<Map<String, dynamic>> refundReasons = [
{'value': 'Barang Rusak', 'icon': Icons.broken_image, 'color': Colors.red},
{
'value': 'Barang Rusak',
'icon': Icons.broken_image,
'color': AppColors.primary
},
{'value': 'Salah Item', 'icon': Icons.swap_horiz, 'color': Colors.orange},
{
'value': 'Tidak Sesuai Pesanan',
@@ -56,7 +60,11 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
'icon': Icons.thumb_down,
'color': Colors.purple
},
{'value': 'Lainnya', 'icon': Icons.more_horiz, 'color': Colors.grey},
{
'value': 'Lainnya',
'icon': Icons.more_horiz,
'color': Colors.red,
},
];
@override
@@ -201,13 +209,6 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
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),
@@ -220,16 +221,12 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.green[400]!, Colors.green[600]!],
colors: [
const Color.fromARGB(255, 96, 56, 148),
AppColors.primary
],
),
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.green.withOpacity(0.3),
blurRadius: 15,
offset: Offset(0, 8),
),
],
),
child: Icon(
Icons.receipt_long,
@@ -327,13 +324,6 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
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),
@@ -345,12 +335,17 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.amber.withOpacity(0.2),
gradient: LinearGradient(
colors: [
const Color.fromARGB(255, 96, 56, 148),
AppColors.primary
],
),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
Icons.account_balance_wallet,
color: Colors.amber[700],
Icons.account_balance_wallet_outlined,
color: AppColors.white,
size: 24,
),
),
@@ -402,13 +397,6 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
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),
@@ -421,19 +409,15 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
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),
),
colors: [
const Color.fromARGB(255, 96, 56, 148),
AppColors.primary
],
),
borderRadius: BorderRadius.circular(16),
),
child: Icon(
Icons.assignment_return,
Icons.assignment_return_outlined,
color: Colors.white,
size: 24,
),
@@ -655,14 +639,14 @@ class _RefundPageState extends State<RefundPage> with TickerProviderStateMixin {
orElse: () => false,
);
return Container(
return SizedBox(
width: double.infinity,
height: 64,
child: ElevatedButton(
onPressed:
isLoading ? null : () => _processRefund(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red[600],
backgroundColor: AppColors.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
@@ -172,12 +172,17 @@ class ProfitLossWidget extends StatelessWidget {
break;
}
if (previous == 0) return '+0.0%';
// Handle division by zero and invalid values
if (previous == 0 || previous.isNaN || previous.isInfinite) return '+0.0%';
if (current.isNaN || current.isInfinite) return '+0.0%';
final trendPercentage = ((current - previous) / previous) * 100;
final sign = trendPercentage >= 0 ? '+' : '';
return '$sign${trendPercentage.toStringAsFixed(1)}%';
// Check if trendPercentage is valid
if (trendPercentage.isNaN || trendPercentage.isInfinite) return '+0.0%';
final sign = trendPercentage >= 0 ? '+' : '';
return '$sign${trendPercentage.round()}%';
}
Widget _buildSummaryCards() {
@@ -201,8 +206,7 @@ class ProfitLossWidget extends StatelessWidget {
{
'title': 'Laba Kotor',
'value': _formatCurrency(data.summary.grossProfit),
'subtitle':
'${data.summary.grossProfitMargin.toStringAsFixed(1)}% margin',
'subtitle': '${_safeRound(data.summary.grossProfitMargin)}% margin',
'icon': Icons.trending_up,
'color': AppColorProfitLoss.primary,
'trend': _calculateTrend('grossProfit'),
@@ -210,8 +214,7 @@ class ProfitLossWidget extends StatelessWidget {
{
'title': 'Laba Bersih',
'value': _formatCurrency(data.summary.netProfit),
'subtitle':
'${data.summary.netProfitMargin.toStringAsFixed(1)}% margin',
'subtitle': '${_safeRound(data.summary.netProfitMargin)}% margin',
'icon': Icons.account_balance,
'color': AppColorProfitLoss.info,
'trend': _calculateTrend('netProfit'),
@@ -360,13 +363,17 @@ class ProfitLossWidget extends StatelessWidget {
showTitles: true,
reservedSize: 50,
getTitlesWidget: (value, meta) {
final kValue = (value / 1000);
if (kValue.isFinite) {
return Text(
'${(value / 1000).toInt()}K',
'${kValue.toInt()}K',
style: TextStyle(
color: Colors.grey[600],
fontSize: 10,
),
);
}
return const SizedBox();
},
),
),
@@ -402,8 +409,9 @@ class ProfitLossWidget extends StatelessWidget {
// Garis Pendapatan
LineChartBarData(
spots: data.data.asMap().entries.map((entry) {
final revenue = entry.value.revenue.toDouble();
return FlSpot(
entry.key.toDouble(), entry.value.revenue.toDouble());
entry.key.toDouble(), revenue.isFinite ? revenue : 0);
}).toList(),
isCurved: true,
color: AppColorProfitLoss.info,
@@ -412,8 +420,9 @@ class ProfitLossWidget extends StatelessWidget {
// Garis Biaya
LineChartBarData(
spots: data.data.asMap().entries.map((entry) {
final cost = entry.value.cost.toDouble();
return FlSpot(
entry.key.toDouble(), entry.value.cost.toDouble());
entry.key.toDouble(), cost.isFinite ? cost : 0);
}).toList(),
isCurved: true,
color: AppColorProfitLoss.danger,
@@ -422,8 +431,9 @@ class ProfitLossWidget extends StatelessWidget {
// Garis Laba Bersih
LineChartBarData(
spots: data.data.asMap().entries.map((entry) {
final netProfit = entry.value.netProfit.toDouble();
return FlSpot(entry.key.toDouble(),
entry.value.netProfit.toDouble());
netProfit.isFinite ? netProfit : 0);
}).toList(),
isCurved: true,
color: AppColorProfitLoss.success,
@@ -506,9 +516,10 @@ class ProfitLossWidget extends StatelessWidget {
}
Widget _buildProductItem(ProfitLossProduct product) {
final profitColor = product.grossProfitMargin >= 35
final profitMargin = _safeDouble(product.grossProfitMargin);
final profitColor = profitMargin >= 35
? AppColorProfitLoss.success
: product.grossProfitMargin >= 25
: profitMargin >= 25
? AppColorProfitLoss.warning
: AppColorProfitLoss.danger;
@@ -572,7 +583,7 @@ class ProfitLossWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(6),
),
child: Text(
'${product.grossProfitMargin.toStringAsFixed(1)}%',
'${_safeRound(profitMargin)}%',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
@@ -710,13 +721,17 @@ class ProfitLossWidget extends StatelessWidget {
startDegreeOffset: -90,
sections: breakdownData.asMap().entries.map((entry) {
final item = entry.value;
final grossProfit = data.summary.grossProfit;
final value = item['value'] as int;
// Handle division by zero
final percentage =
(item['value'] as int) / data.summary.grossProfit * 100;
grossProfit > 0 ? (value / grossProfit * 100) : 0.0;
return PieChartSectionData(
color: item['color'] as Color,
value: (item['value'] as int).toDouble(),
title: '${percentage.toStringAsFixed(1)}%',
value: value.toDouble(),
title: '${_safeRound(percentage)}%',
radius: 40,
titleStyle: const TextStyle(
fontSize: 10,
@@ -799,7 +814,7 @@ class ProfitLossWidget extends StatelessWidget {
),
const SizedBox(height: 8),
Text(
'${(data.summary.profitabilityRatio * 100).toStringAsFixed(1)}%',
'${_safeRound(data.summary.profitabilityRatio)}%',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
@@ -848,9 +863,7 @@ class ProfitLossWidget extends StatelessWidget {
Expanded(
child: _buildMetricCard(
'Nilai Rata-rata Pesanan',
_formatCurrency(
(data.summary.totalRevenue / data.summary.totalOrders)
.round()),
_formatCurrency(_safeCalculateAverageOrder()),
'Per transaksi',
Icons.shopping_cart_outlined,
AppColorProfitLoss.info,
@@ -870,7 +883,7 @@ class ProfitLossWidget extends StatelessWidget {
Expanded(
child: _buildMetricCard(
'Rasio Biaya',
'${((data.summary.totalCost / data.summary.totalRevenue) * 100).toStringAsFixed(1)}%',
'${_safeCalculateCostRatio()}%',
'Dari total pendapatan',
Icons.pie_chart,
AppColorProfitLoss.danger,
@@ -1026,7 +1039,7 @@ class ProfitLossWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(4),
),
child: Text(
'${item.netProfitMargin.toStringAsFixed(1)}%',
'${_safeRound(item.netProfitMargin)}%',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
@@ -1093,6 +1106,31 @@ class ProfitLossWidget extends StatelessWidget {
);
}
// Helper methods for safe calculations
int _safeRound(double value) {
if (value.isNaN || value.isInfinite) return 0;
return value.round();
}
double _safeDouble(double value) {
if (value.isNaN || value.isInfinite) return 0.0;
return value;
}
int _safeCalculateAverageOrder() {
if (data.summary.totalOrders == 0) return 0;
final average = data.summary.totalRevenue / data.summary.totalOrders;
if (average.isNaN || average.isInfinite) return 0;
return average.round();
}
int _safeCalculateCostRatio() {
if (data.summary.totalRevenue == 0) return 0;
final ratio = (data.summary.totalCost / data.summary.totalRevenue) * 100;
if (ratio.isNaN || ratio.isInfinite) return 0;
return ratio.round();
}
IconData _getProductIcon(String category) {
switch (category.toLowerCase()) {
case 'coffee':
@@ -1110,8 +1148,9 @@ class ProfitLossWidget extends StatelessWidget {
}
Color _getMarginColor(double margin) {
if (margin >= 25) return AppColorProfitLoss.success;
if (margin >= 15) return AppColorProfitLoss.warning;
final safeMargin = _safeDouble(margin);
if (safeMargin >= 25) return AppColorProfitLoss.success;
if (safeMargin >= 15) return AppColorProfitLoss.warning;
return AppColorProfitLoss.danger;
}
@@ -1126,7 +1165,7 @@ class ProfitLossWidget extends StatelessWidget {
String _formatCurrencyShort(int amount) {
if (amount >= 1000000) {
return 'Rp ${(amount / 1000000).toStringAsFixed(1)}M';
return 'Rp ${(amount / 1000000).round()}M';
} else if (amount >= 1000) {
return 'Rp ${(amount / 1000).toStringAsFixed(0)}K';
}
@@ -60,6 +60,7 @@ class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
status: event.status,
dateFrom: event.dateFrom,
dateTo: event.dateTo,
search: event.search,
);
await result.fold(
@@ -108,6 +109,7 @@ class OrderLoaderBloc extends Bloc<OrderLoaderEvent, OrderLoaderState> {
status: event.status,
dateFrom: event.dateFrom,
dateTo: event.dateTo,
search: event.search,
);
await result.fold(
@@ -18,33 +18,36 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$OrderLoaderEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)
required TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)
getByStatus,
required TResult Function(String id) getById,
required TResult Function(String status, DateTime dateFrom, DateTime dateTo)
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)
loadMore,
required TResult Function(String status) refresh,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult? Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult? Function(String id)? getById,
TResult? Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult? Function(String status)? refresh,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult Function(String id)? getById,
TResult Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult Function(String status)? refresh,
required TResult orElse(),
@@ -104,7 +107,12 @@ abstract class _$$GetByStatusImplCopyWith<$Res> {
_$GetByStatusImpl value, $Res Function(_$GetByStatusImpl) then) =
__$$GetByStatusImplCopyWithImpl<$Res>;
@useResult
$Res call({String status, DateTime dateFrom, DateTime dateTo, int? limit});
$Res call(
{String status,
DateTime dateFrom,
DateTime dateTo,
int? limit,
String? search});
}
/// @nodoc
@@ -124,6 +132,7 @@ class __$$GetByStatusImplCopyWithImpl<$Res>
Object? dateFrom = null,
Object? dateTo = null,
Object? limit = freezed,
Object? search = freezed,
}) {
return _then(_$GetByStatusImpl(
null == status
@@ -142,6 +151,10 @@ class __$$GetByStatusImplCopyWithImpl<$Res>
? _value.limit
: limit // ignore: cast_nullable_to_non_nullable
as int?,
search: freezed == search
? _value.search
: search // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
@@ -150,7 +163,7 @@ class __$$GetByStatusImplCopyWithImpl<$Res>
class _$GetByStatusImpl implements _GetByStatus {
const _$GetByStatusImpl(this.status,
{required this.dateFrom, required this.dateTo, this.limit});
{required this.dateFrom, required this.dateTo, this.limit, this.search});
@override
final String status;
@@ -160,10 +173,12 @@ class _$GetByStatusImpl implements _GetByStatus {
final DateTime dateTo;
@override
final int? limit;
@override
final String? search;
@override
String toString() {
return 'OrderLoaderEvent.getByStatus(status: $status, dateFrom: $dateFrom, dateTo: $dateTo, limit: $limit)';
return 'OrderLoaderEvent.getByStatus(status: $status, dateFrom: $dateFrom, dateTo: $dateTo, limit: $limit, search: $search)';
}
@override
@@ -175,11 +190,13 @@ class _$GetByStatusImpl implements _GetByStatus {
(identical(other.dateFrom, dateFrom) ||
other.dateFrom == dateFrom) &&
(identical(other.dateTo, dateTo) || other.dateTo == dateTo) &&
(identical(other.limit, limit) || other.limit == limit));
(identical(other.limit, limit) || other.limit == limit) &&
(identical(other.search, search) || other.search == search));
}
@override
int get hashCode => Object.hash(runtimeType, status, dateFrom, dateTo, limit);
int get hashCode =>
Object.hash(runtimeType, status, dateFrom, dateTo, limit, search);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@@ -192,45 +209,48 @@ class _$GetByStatusImpl implements _GetByStatus {
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)
required TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)
getByStatus,
required TResult Function(String id) getById,
required TResult Function(String status, DateTime dateFrom, DateTime dateTo)
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)
loadMore,
required TResult Function(String status) refresh,
}) {
return getByStatus(status, dateFrom, dateTo, limit);
return getByStatus(status, dateFrom, dateTo, limit, search);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult? Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult? Function(String id)? getById,
TResult? Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult? Function(String status)? refresh,
}) {
return getByStatus?.call(status, dateFrom, dateTo, limit);
return getByStatus?.call(status, dateFrom, dateTo, limit, search);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult Function(String id)? getById,
TResult Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult Function(String status)? refresh,
required TResult orElse(),
}) {
if (getByStatus != null) {
return getByStatus(status, dateFrom, dateTo, limit);
return getByStatus(status, dateFrom, dateTo, limit, search);
}
return orElse();
}
@@ -277,12 +297,14 @@ abstract class _GetByStatus implements OrderLoaderEvent {
const factory _GetByStatus(final String status,
{required final DateTime dateFrom,
required final DateTime dateTo,
final int? limit}) = _$GetByStatusImpl;
final int? limit,
final String? search}) = _$GetByStatusImpl;
String get status;
DateTime get dateFrom;
DateTime get dateTo;
int? get limit;
String? get search;
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@@ -359,11 +381,12 @@ class _$GetByIdImpl implements _GetById {
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)
required TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)
getByStatus,
required TResult Function(String id) getById,
required TResult Function(String status, DateTime dateFrom, DateTime dateTo)
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)
loadMore,
required TResult Function(String status) refresh,
}) {
@@ -373,11 +396,12 @@ class _$GetByIdImpl implements _GetById {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult? Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult? Function(String id)? getById,
TResult? Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult? Function(String status)? refresh,
}) {
@@ -387,11 +411,12 @@ class _$GetByIdImpl implements _GetById {
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult Function(String id)? getById,
TResult Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult Function(String status)? refresh,
required TResult orElse(),
@@ -458,7 +483,8 @@ abstract class _$$LoadMoreImplCopyWith<$Res> {
_$LoadMoreImpl value, $Res Function(_$LoadMoreImpl) then) =
__$$LoadMoreImplCopyWithImpl<$Res>;
@useResult
$Res call({String status, DateTime dateFrom, DateTime dateTo});
$Res call(
{String status, DateTime dateFrom, DateTime dateTo, String? search});
}
/// @nodoc
@@ -477,6 +503,7 @@ class __$$LoadMoreImplCopyWithImpl<$Res>
Object? status = null,
Object? dateFrom = null,
Object? dateTo = null,
Object? search = freezed,
}) {
return _then(_$LoadMoreImpl(
null == status
@@ -491,6 +518,10 @@ class __$$LoadMoreImplCopyWithImpl<$Res>
? _value.dateTo
: dateTo // ignore: cast_nullable_to_non_nullable
as DateTime,
search: freezed == search
? _value.search
: search // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
@@ -499,7 +530,7 @@ class __$$LoadMoreImplCopyWithImpl<$Res>
class _$LoadMoreImpl implements _LoadMore {
const _$LoadMoreImpl(this.status,
{required this.dateFrom, required this.dateTo});
{required this.dateFrom, required this.dateTo, this.search});
@override
final String status;
@@ -507,10 +538,12 @@ class _$LoadMoreImpl implements _LoadMore {
final DateTime dateFrom;
@override
final DateTime dateTo;
@override
final String? search;
@override
String toString() {
return 'OrderLoaderEvent.loadMore(status: $status, dateFrom: $dateFrom, dateTo: $dateTo)';
return 'OrderLoaderEvent.loadMore(status: $status, dateFrom: $dateFrom, dateTo: $dateTo, search: $search)';
}
@override
@@ -521,11 +554,13 @@ class _$LoadMoreImpl implements _LoadMore {
(identical(other.status, status) || other.status == status) &&
(identical(other.dateFrom, dateFrom) ||
other.dateFrom == dateFrom) &&
(identical(other.dateTo, dateTo) || other.dateTo == dateTo));
(identical(other.dateTo, dateTo) || other.dateTo == dateTo) &&
(identical(other.search, search) || other.search == search));
}
@override
int get hashCode => Object.hash(runtimeType, status, dateFrom, dateTo);
int get hashCode =>
Object.hash(runtimeType, status, dateFrom, dateTo, search);
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@@ -538,45 +573,48 @@ class _$LoadMoreImpl implements _LoadMore {
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)
required TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)
getByStatus,
required TResult Function(String id) getById,
required TResult Function(String status, DateTime dateFrom, DateTime dateTo)
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)
loadMore,
required TResult Function(String status) refresh,
}) {
return loadMore(status, dateFrom, dateTo);
return loadMore(status, dateFrom, dateTo, search);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult? Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult? Function(String id)? getById,
TResult? Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult? Function(String status)? refresh,
}) {
return loadMore?.call(status, dateFrom, dateTo);
return loadMore?.call(status, dateFrom, dateTo, search);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult Function(String id)? getById,
TResult Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult Function(String status)? refresh,
required TResult orElse(),
}) {
if (loadMore != null) {
return loadMore(status, dateFrom, dateTo);
return loadMore(status, dateFrom, dateTo, search);
}
return orElse();
}
@@ -622,11 +660,13 @@ class _$LoadMoreImpl implements _LoadMore {
abstract class _LoadMore implements OrderLoaderEvent {
const factory _LoadMore(final String status,
{required final DateTime dateFrom,
required final DateTime dateTo}) = _$LoadMoreImpl;
required final DateTime dateTo,
final String? search}) = _$LoadMoreImpl;
String get status;
DateTime get dateFrom;
DateTime get dateTo;
String? get search;
/// Create a copy of OrderLoaderEvent
/// with the given fields replaced by the non-null parameter values.
@@ -703,11 +743,12 @@ class _$RefreshImpl implements _Refresh {
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)
required TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)
getByStatus,
required TResult Function(String id) getById,
required TResult Function(String status, DateTime dateFrom, DateTime dateTo)
required TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)
loadMore,
required TResult Function(String status) refresh,
}) {
@@ -717,11 +758,12 @@ class _$RefreshImpl implements _Refresh {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult? Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult? Function(String id)? getById,
TResult? Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult? Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult? Function(String status)? refresh,
}) {
@@ -731,11 +773,12 @@ class _$RefreshImpl implements _Refresh {
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, int? limit)?
TResult Function(String status, DateTime dateFrom, DateTime dateTo,
int? limit, String? search)?
getByStatus,
TResult Function(String id)? getById,
TResult Function(String status, DateTime dateFrom, DateTime dateTo)?
TResult Function(
String status, DateTime dateFrom, DateTime dateTo, String? search)?
loadMore,
TResult Function(String status)? refresh,
required TResult orElse(),
@@ -2,15 +2,19 @@ part of 'order_loader_bloc.dart';
@freezed
class OrderLoaderEvent with _$OrderLoaderEvent {
const factory OrderLoaderEvent.getByStatus(String status,
{required DateTime dateFrom,
const factory OrderLoaderEvent.getByStatus(
String status, {
required DateTime dateFrom,
required DateTime dateTo,
int? limit}) = _GetByStatus;
int? limit,
String? search,
}) = _GetByStatus;
const factory OrderLoaderEvent.getById(String id) = _GetById;
const factory OrderLoaderEvent.loadMore(
String status, {
required DateTime dateFrom,
required DateTime dateTo,
String? search,
}) = _LoadMore;
const factory OrderLoaderEvent.refresh(String status) = _Refresh;
}
@@ -29,5 +29,22 @@ class PaymentFormBloc extends Bloc<PaymentFormEvent, PaymentFormState> {
}
},
);
on<_CreateSplitBill>(
(event, emit) async {
emit(const _Loading());
try {
final result = await _orderRemoteDatasource
.createPaymentSplitBill(event.payment);
result.fold(
(error) => emit(_Error(error)),
(success) => emit(_Success(success.data!)),
);
} catch (e) {
emit(_Error("Failed to create payment split bill: $e"));
}
},
);
}
}
@@ -16,45 +16,45 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$PaymentFormEvent {
PaymentRequestModel get payment => throw _privateConstructorUsedError;
Object get payment => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(PaymentRequestModel payment) create,
required TResult Function(PaymentSplitBillRequest payment) createSplitBill,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(PaymentRequestModel payment)? create,
TResult? Function(PaymentSplitBillRequest payment)? createSplitBill,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(PaymentRequestModel payment)? create,
TResult Function(PaymentSplitBillRequest payment)? createSplitBill,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Create value) create,
required TResult Function(_CreateSplitBill value) createSplitBill,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Create value)? create,
TResult? Function(_CreateSplitBill value)? createSplitBill,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Create value)? create,
TResult Function(_CreateSplitBill value)? createSplitBill,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$PaymentFormEventCopyWith<PaymentFormEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
@@ -62,8 +62,6 @@ abstract class $PaymentFormEventCopyWith<$Res> {
factory $PaymentFormEventCopyWith(
PaymentFormEvent value, $Res Function(PaymentFormEvent) then) =
_$PaymentFormEventCopyWithImpl<$Res, PaymentFormEvent>;
@useResult
$Res call({PaymentRequestModel payment});
}
/// @nodoc
@@ -78,27 +76,13 @@ class _$PaymentFormEventCopyWithImpl<$Res, $Val extends PaymentFormEvent>
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? payment = null,
}) {
return _then(_value.copyWith(
payment: null == payment
? _value.payment
: payment // ignore: cast_nullable_to_non_nullable
as PaymentRequestModel,
) as $Val);
}
}
/// @nodoc
abstract class _$$CreateImplCopyWith<$Res>
implements $PaymentFormEventCopyWith<$Res> {
abstract class _$$CreateImplCopyWith<$Res> {
factory _$$CreateImplCopyWith(
_$CreateImpl value, $Res Function(_$CreateImpl) then) =
__$$CreateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({PaymentRequestModel payment});
}
@@ -163,6 +147,7 @@ class _$CreateImpl implements _Create {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(PaymentRequestModel payment) create,
required TResult Function(PaymentSplitBillRequest payment) createSplitBill,
}) {
return create(payment);
}
@@ -171,6 +156,7 @@ class _$CreateImpl implements _Create {
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(PaymentRequestModel payment)? create,
TResult? Function(PaymentSplitBillRequest payment)? createSplitBill,
}) {
return create?.call(payment);
}
@@ -179,6 +165,7 @@ class _$CreateImpl implements _Create {
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(PaymentRequestModel payment)? create,
TResult Function(PaymentSplitBillRequest payment)? createSplitBill,
required TResult orElse(),
}) {
if (create != null) {
@@ -191,6 +178,7 @@ class _$CreateImpl implements _Create {
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Create value) create,
required TResult Function(_CreateSplitBill value) createSplitBill,
}) {
return create(this);
}
@@ -199,6 +187,7 @@ class _$CreateImpl implements _Create {
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Create value)? create,
TResult? Function(_CreateSplitBill value)? createSplitBill,
}) {
return create?.call(this);
}
@@ -207,6 +196,7 @@ class _$CreateImpl implements _Create {
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Create value)? create,
TResult Function(_CreateSplitBill value)? createSplitBill,
required TResult orElse(),
}) {
if (create != null) {
@@ -224,12 +214,154 @@ abstract class _Create implements PaymentFormEvent {
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CreateImplCopyWith<_$CreateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$CreateSplitBillImplCopyWith<$Res> {
factory _$$CreateSplitBillImplCopyWith(_$CreateSplitBillImpl value,
$Res Function(_$CreateSplitBillImpl) then) =
__$$CreateSplitBillImplCopyWithImpl<$Res>;
@useResult
$Res call({PaymentSplitBillRequest payment});
}
/// @nodoc
class __$$CreateSplitBillImplCopyWithImpl<$Res>
extends _$PaymentFormEventCopyWithImpl<$Res, _$CreateSplitBillImpl>
implements _$$CreateSplitBillImplCopyWith<$Res> {
__$$CreateSplitBillImplCopyWithImpl(
_$CreateSplitBillImpl _value, $Res Function(_$CreateSplitBillImpl) _then)
: super(_value, _then);
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? payment = null,
}) {
return _then(_$CreateSplitBillImpl(
null == payment
? _value.payment
: payment // ignore: cast_nullable_to_non_nullable
as PaymentSplitBillRequest,
));
}
}
/// @nodoc
class _$CreateSplitBillImpl implements _CreateSplitBill {
const _$CreateSplitBillImpl(this.payment);
@override
final PaymentSplitBillRequest payment;
@override
String toString() {
return 'PaymentFormEvent.createSplitBill(payment: $payment)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CreateSplitBillImpl &&
(identical(other.payment, payment) || other.payment == payment));
}
@override
int get hashCode => Object.hash(runtimeType, payment);
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$CreateSplitBillImplCopyWith<_$CreateSplitBillImpl> get copyWith =>
__$$CreateSplitBillImplCopyWithImpl<_$CreateSplitBillImpl>(
this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(PaymentRequestModel payment) create,
required TResult Function(PaymentSplitBillRequest payment) createSplitBill,
}) {
return createSplitBill(payment);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(PaymentRequestModel payment)? create,
TResult? Function(PaymentSplitBillRequest payment)? createSplitBill,
}) {
return createSplitBill?.call(payment);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(PaymentRequestModel payment)? create,
TResult Function(PaymentSplitBillRequest payment)? createSplitBill,
required TResult orElse(),
}) {
if (createSplitBill != null) {
return createSplitBill(payment);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Create value) create,
required TResult Function(_CreateSplitBill value) createSplitBill,
}) {
return createSplitBill(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Create value)? create,
TResult? Function(_CreateSplitBill value)? createSplitBill,
}) {
return createSplitBill?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Create value)? create,
TResult Function(_CreateSplitBill value)? createSplitBill,
required TResult orElse(),
}) {
if (createSplitBill != null) {
return createSplitBill(this);
}
return orElse();
}
}
abstract class _CreateSplitBill implements PaymentFormEvent {
const factory _CreateSplitBill(final PaymentSplitBillRequest payment) =
_$CreateSplitBillImpl;
@override
PaymentSplitBillRequest get payment;
/// Create a copy of PaymentFormEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$CreateSplitBillImplCopyWith<_$CreateSplitBillImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$PaymentFormState {
@optionalTypeArgs
@@ -2,5 +2,10 @@ part of 'payment_form_bloc.dart';
@freezed
class PaymentFormEvent with _$PaymentFormEvent {
const factory PaymentFormEvent.create(PaymentRequestModel payment) = _Create;
const factory PaymentFormEvent.create(
PaymentRequestModel payment,
) = _Create;
const factory PaymentFormEvent.createSplitBill(
PaymentSplitBillRequest payment,
) = _CreateSplitBill;
}
@@ -343,6 +343,8 @@ class _PaymentDialogState extends State<PaymentDialog> {
.toList() ??
[],
payment: data,
paymentMethod: selectedPaymentMethod?.name ?? "",
nominalBayar: totalPriceController.text.toIntegerFromText,
));
},
error: (message) {
+41 -39
View File
@@ -6,9 +6,9 @@ import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dar
import 'package:enaklo_pos/presentation/payment/pages/payment_page.dart';
import 'package:enaklo_pos/presentation/refund/pages/refund_page.dart';
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_payment_summary.dart';
import 'package:enaklo_pos/presentation/split_bill/pages/split_bill_page.dart';
import 'package:enaklo_pos/presentation/void/pages/void_page.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_detail.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_list_order.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_order_information.dart';
import 'package:enaklo_pos/presentation/sales/widgets/sales_payment.dart';
@@ -45,18 +45,6 @@ class _SalesPageState extends State<SalesPage> {
super.initState();
}
List<Order> _filterOrders(List<Order> orders) {
if (searchQuery.isEmpty) {
return orders;
}
return orders.where((order) {
final customerName = order.orderNumber?.toLowerCase() ?? "";
final queryLower = searchQuery.toLowerCase();
return customerName.contains(queryLower);
}).toList();
}
@override
Widget build(BuildContext context) {
return SafeArea(
@@ -71,8 +59,13 @@ class _SalesPageState extends State<SalesPage> {
if (notification is ScrollEndNotification &&
scrollController.position.extentAfter == 0) {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.loadMore(widget.status,
dateFrom: startDate, dateTo: endDate));
OrderLoaderEvent.loadMore(
widget.status,
dateFrom: startDate,
dateTo: endDate,
search: searchQuery,
),
);
return true;
}
@@ -92,6 +85,16 @@ class _SalesPageState extends State<SalesPage> {
setState(() {
searchQuery = value;
});
Future.delayed(const Duration(milliseconds: 800), () {
context.read<OrderLoaderBloc>().add(
OrderLoaderEvent.getByStatus(
widget.status,
dateFrom: startDate,
dateTo: endDate,
search: searchQuery,
),
);
});
},
onDateRangeChanged: (start, end) {
setState(() {
@@ -125,8 +128,7 @@ class _SalesPageState extends State<SalesPage> {
),
loaded: (orders, totalOrder, hasReachedMax,
currentPage, isLoadingMore) {
final filtered = _filterOrders(orders);
if (filtered.isEmpty) {
if (orders.isEmpty) {
return Center(
child: Text(
"Belum ada transaksi saat ini. ",
@@ -138,17 +140,17 @@ class _SalesPageState extends State<SalesPage> {
);
} else {
return ListView.builder(
itemCount: filtered.length,
itemCount: orders.length,
controller: scrollController,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
orderDetail = filtered[index];
orderDetail = orders[index];
});
context.read<OrderFormBloc>().add(
OrderFormEvent.started(
filtered[index]));
orders[index]));
},
child: SalesCard(
order: orders[index],
@@ -257,35 +259,35 @@ class _SalesPageState extends State<SalesPage> {
),
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: SalesOrderInformation(
// Order Header
SalesOrderInformation(
order: orderDetail,
),
),
SpaceWidth(16),
Expanded(
child: SalesDetail(
order: orderDetail,
),
),
// Order Items
SalesListOrder(order: orderDetail),
const SpaceHeight(16),
// Payment Summary
SalesPaymentSummary(order: orderDetail),
const SpaceHeight(16),
// Payment Information
if (orderDetail?.payments != null &&
orderDetail?.payments!.isNotEmpty ==
true) ...[
SalesPayment(order: orderDetail),
const SpaceHeight(20),
],
),
SalesListOrder(
order: orderDetail,
),
SalesPayment(
order: orderDetail,
),
],
),
),
)
),
),
],
),
),
+104 -22
View File
@@ -29,8 +29,9 @@ class SalesCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerRight,
Row(
children: [
Expanded(
child: Text(
'${order.orderNumber}',
style: const TextStyle(
@@ -39,10 +40,46 @@ class SalesCard extends StatelessWidget {
),
),
),
if (order.isRefund == true)
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Refund',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: 10,
letterSpacing: 0.5,
),
),
),
if (order.isVoid == true)
Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'Void',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
fontSize: 10,
letterSpacing: 0.5,
),
),
),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 22,
@@ -63,6 +100,7 @@ class SalesCard extends StatelessWidget {
fontWeight: FontWeight.w600,
),
),
if (order.orderType == "dineIn") ...[
const SizedBox(height: 4),
Row(
children: [
@@ -74,26 +112,11 @@ class SalesCard extends StatelessWidget {
),
],
),
]
],
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
child: Text(
(order.status ?? "").toUpperCase(),
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.w600,
fontSize: 12,
letterSpacing: 0.5,
),
),
),
_buildStatus(),
],
),
const SizedBox(height: 16),
@@ -101,7 +124,10 @@ class SalesCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
(order.totalAmount ?? 0).currencyFormatRpV2,
order.status == 'pending'
? ((order.totalAmount ?? 0) - (order.totalPaid ?? 0))
.currencyFormatRpV2
: (order.totalAmount ?? 0).currencyFormatRpV2,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
@@ -121,4 +147,60 @@ class SalesCard extends StatelessWidget {
),
);
}
Widget _buildStatus() {
switch (order.status) {
case 'pending':
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.amber.withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
child: Text(
(order.status ?? "").toUpperCase(),
style: TextStyle(
color: Colors.amber,
fontWeight: FontWeight.w600,
fontSize: 12,
letterSpacing: 0.5,
),
),
);
case 'completed':
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
child: Text(
(order.status ?? "").toUpperCase(),
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.w600,
fontSize: 12,
letterSpacing: 0.5,
),
),
);
default:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
child: Text(
(order.status ?? "").toUpperCase(),
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.w600,
fontSize: 12,
letterSpacing: 0.5,
),
),
);
}
}
}
@@ -1,71 +0,0 @@
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:flutter/material.dart';
class SalesDetail extends StatelessWidget {
final Order? order;
const SalesDetail({super.key, this.order});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Detail',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
_item(
title: 'Pelanggan',
value: order?.metadata?['customer_name'] ?? "-",
),
_item(
title: 'Waktu',
value: (order?.createdAt ?? DateTime.now()).toFormattedDate3(),
),
_item(
title: 'Status',
value: order?.status ?? "-",
),
],
),
);
}
Padding _item({
required String title,
required String value,
}) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
),
),
Text(
value,
style: const TextStyle(
fontSize: 14,
),
),
],
),
);
}
}
@@ -63,27 +63,6 @@ class SalesListOrder extends StatelessWidget {
),
child: Row(
children: [
Container(
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isAllSelected ? AppColors.primary : Colors.grey.shade300,
width: 2,
),
),
child: Checkbox(
value: isAllSelected,
activeColor: AppColors.primary,
checkColor: AppColors.white,
onChanged: (val) {
context
.read<OrderFormBloc>()
.add(OrderFormEvent.toggleSelectAll(val ?? false));
},
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -179,28 +158,6 @@ class SalesListOrder extends StatelessWidget {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color:
isSelected ? AppColors.primary : Colors.grey.shade300,
width: 1.5,
),
),
child: Checkbox(
value: isSelected,
activeColor: AppColors.primary,
checkColor: AppColors.white,
onChanged: (_) {
context
.read<OrderFormBloc>()
.add(OrderFormEvent.toggleItem(product));
},
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -314,6 +271,57 @@ class SalesListOrder extends StatelessWidget {
),
],
),
if (order?.splitType == 'ITEM' && order?.status == 'pending') ...[
SpaceHeight(6),
Align(
alignment: Alignment.centerRight,
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColors.primary),
),
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: '${product.paidQuantity} ',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.primary,
),
),
TextSpan(
text: 'dari ',
style: const TextStyle(
fontSize: 12,
color: AppColors.primary,
),
),
TextSpan(
text: '${product.quantity} ',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColors.primary,
),
),
TextSpan(
text: 'kuantiti telah dibayar.',
style: const TextStyle(
fontSize: 12,
color: AppColors.primary,
),
),
],
),
),
),
)
],
],
),
),
@@ -328,8 +336,8 @@ class SalesListOrder extends StatelessWidget {
switch (status) {
case "pending":
backgroundColor = Colors.orange.withOpacity(0.1);
textColor = Colors.orange.shade700;
backgroundColor = Colors.white;
textColor = Colors.white;
displayText = "Pending";
icon = Icons.access_time;
break;
@@ -1,3 +1,4 @@
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/data/models/response/order_response_model.dart';
@@ -10,66 +11,121 @@ class SalesOrderInformation extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColors.white,
color: AppColors.primary,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Informasi Pesanan',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
order?.orderNumber ?? "",
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
_item(
title: 'No. Order',
value: "${order?.orderNumber}",
_buildStatus(),
],
),
_item(
title: 'Tanggal',
value: (order?.createdAt ?? DateTime.now()).toFormattedDate2(),
const SizedBox(height: 8),
Row(
children: [
if (order?.orderType == 'dineIn') ...[
_buildRowItem(Icons.table_restaurant_outlined,
'Meja ${order?.tableNumber}'),
const SizedBox(width: 16),
],
_buildRowItem(Icons.restaurant_outlined, '${order?.orderType}'),
],
),
_item(
title: 'No. Meja',
value: order?.tableNumber ?? "-",
const SizedBox(height: 8),
Text(
'Pelanggan: ${order?.metadata?['customer_name'] ?? ""}',
style: const TextStyle(color: Colors.white, fontSize: 14),
),
_item(
title: 'Jenis Order',
value: order?.orderType ?? "-",
Text(
'Dibuat: ${order?.createdAt?.toFormattedDate3() ?? ""}',
style: const TextStyle(color: Colors.white70, fontSize: 12),
),
],
),
);
}
Padding _item({
required String title,
required String value,
}) {
return Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Row _buildRowItem(IconData icon, String title) {
return Row(
children: [
Icon(
icon,
color: Colors.white70,
size: 16,
),
const SpaceWidth(4),
Text(
title,
style: const TextStyle(
fontSize: 14,
),
),
Text(
value,
style: const TextStyle(
fontSize: 14,
),
style: const TextStyle(color: Colors.white70, fontSize: 14),
),
],
);
}
Container _buildStatus() {
switch (order?.status) {
case 'pending':
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(12),
),
child: Text(
(order?.status ?? "").toUpperCase(),
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
);
case 'completed':
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.greenAccent,
borderRadius: BorderRadius.circular(12),
),
child: Text(
(order?.status ?? "").toUpperCase(),
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
);
default:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(12),
),
child: Text(
(order?.status ?? "").toUpperCase(),
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
);
}
}
}
+143 -66
View File
@@ -1,7 +1,7 @@
import 'package:enaklo_pos/core/components/dashed_divider.dart';
import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:flutter/material.dart';
@@ -12,88 +12,165 @@ class SalesPayment extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(top: 16),
decoration: BoxDecoration(
color: AppColors.white,
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Informasi Pesanan',
'Informasi Pembayaran',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
SpaceHeight(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Subtotal ${order?.orderItems?.length} Produk',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
Text(
(order?.subtotal)?.currencyFormatRp ?? "0",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
SpaceHeight(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Pajak',
style: const TextStyle(
fontSize: 16,
),
),
Text(
(order?.taxAmount)?.currencyFormatRp ?? "0",
style: const TextStyle(
fontSize: 16,
),
),
],
),
SpaceHeight(12),
DashedDivider(
color: AppColors.stroke,
),
SpaceHeight(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
Text(
(order?.totalAmount)?.currencyFormatRp ?? "0",
style: const TextStyle(
fontWeight: FontWeight.bold,
color: AppColors.primary,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
_buildPaymentStatus(),
],
),
const SpaceHeight(12),
...List.generate(
order?.payments?.length ?? 0,
(index) => Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: _buildPaymentItem(order?.payments?[index] ?? Payment()),
),
),
const SpaceHeight(4),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Jumlah yang Dibayar',
style: TextStyle(color: Colors.grey.shade700),
),
Text(
(order?.totalPaid ?? 0).currencyFormatRpV2,
style: TextStyle(fontWeight: FontWeight.w500),
),
],
),
if (((order?.totalAmount ?? 0) - (order?.totalPaid ?? 0)) != 0) ...[
const SpaceHeight(4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Sisa Tagihan',
style: TextStyle(
color: Colors.red.shade700,
fontWeight: FontWeight.w500,
),
),
Text(
((order?.totalAmount ?? 0) - (order?.totalPaid ?? 0))
.currencyFormatRpV2,
style: TextStyle(
color: Colors.red.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
],
],
),
);
}
Container _buildPaymentStatus() {
switch (order?.paymentStatus) {
case 'completed':
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: AppColors.green.withOpacity(0.2),
borderRadius: BorderRadius.circular(4),
),
child: Text(
(order?.paymentStatus ?? "").toTitleCase(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: AppColors.green,
),
),
);
default:
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.amber.shade100,
borderRadius: BorderRadius.circular(4),
),
child: Text(
(order?.paymentStatus ?? "").toTitleCase(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.amber.shade800,
),
),
);
}
}
Row _buildPaymentItem(Payment payment) {
return Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: Colors.green.shade100,
borderRadius: BorderRadius.circular(4),
),
child: Icon(
Icons.payments,
color: Colors.green.shade700,
size: 16,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
payment.paymentMethodName ?? "",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
if ((payment.splitTotal ?? 0) > 1)
Text(
'Split ${payment.splitNumber ?? 0} of ${payment.splitTotal ?? 0}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
),
Text(
(payment.amount ?? 0).currencyFormatRpV2,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.green,
),
),
],
);
}
}
@@ -0,0 +1,94 @@
import 'package:enaklo_pos/core/components/dashed_divider.dart';
import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:flutter/material.dart';
class SalesPaymentSummary extends StatelessWidget {
final Order? order;
const SalesPaymentSummary({super.key, this.order});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Ringkasan Pembayaran',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
const SpaceHeight(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Subtotal',
style: TextStyle(color: Colors.grey.shade700),
),
Text((order?.subtotal ?? 0).currencyFormatRpV2),
],
),
const SpaceHeight(4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Tax',
style: TextStyle(color: Colors.grey.shade700),
),
Text((order?.taxAmount ?? 0).currencyFormatRpV2),
],
),
const SpaceHeight(4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Discount',
style: TextStyle(color: Colors.grey.shade700),
),
Text((order?.discountAmount ?? 0).currencyFormatRpV2),
],
),
const SpaceHeight(8),
const DashedDivider(
color: AppColors.grey,
),
const SpaceHeight(8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Total',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
(order?.totalAmount ?? 0).currencyFormatRpV2,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
],
),
],
),
);
}
}
@@ -0,0 +1,20 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/print_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'get_printer_ticket_event.dart';
part 'get_printer_ticket_state.dart';
part 'get_printer_ticket_bloc.freezed.dart';
class GetPrinterTicketBloc
extends Bloc<GetPrinterTicketEvent, GetPrinterTicketState> {
GetPrinterTicketBloc() : super(_Initial()) {
on<_Get>((event, emit) async {
emit(_Loading());
final result =
await ProductLocalDatasource.instance.getPrinterByCode('ticket');
emit(_Success(result));
});
}
}
@@ -0,0 +1,608 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'get_printer_ticket_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$GetPrinterTicketEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $GetPrinterTicketEventCopyWith<$Res> {
factory $GetPrinterTicketEventCopyWith(GetPrinterTicketEvent value,
$Res Function(GetPrinterTicketEvent) then) =
_$GetPrinterTicketEventCopyWithImpl<$Res, GetPrinterTicketEvent>;
}
/// @nodoc
class _$GetPrinterTicketEventCopyWithImpl<$Res,
$Val extends GetPrinterTicketEvent>
implements $GetPrinterTicketEventCopyWith<$Res> {
_$GetPrinterTicketEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of GetPrinterTicketEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$GetImplCopyWith<$Res> {
factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) =
__$$GetImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$GetImplCopyWithImpl<$Res>
extends _$GetPrinterTicketEventCopyWithImpl<$Res, _$GetImpl>
implements _$$GetImplCopyWith<$Res> {
__$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$GetImpl implements _Get {
const _$GetImpl();
@override
String toString() {
return 'GetPrinterTicketEvent.get()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$GetImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() get,
}) {
return get();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? get,
}) {
return get?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? get,
required TResult orElse(),
}) {
if (get != null) {
return get();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) {
return get(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) {
return get?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(this);
}
return orElse();
}
}
abstract class _Get implements GetPrinterTicketEvent {
const factory _Get() = _$GetImpl;
}
/// @nodoc
mixin _$GetPrinterTicketState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PrintModel? printer) success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $GetPrinterTicketStateCopyWith<$Res> {
factory $GetPrinterTicketStateCopyWith(GetPrinterTicketState value,
$Res Function(GetPrinterTicketState) then) =
_$GetPrinterTicketStateCopyWithImpl<$Res, GetPrinterTicketState>;
}
/// @nodoc
class _$GetPrinterTicketStateCopyWithImpl<$Res,
$Val extends GetPrinterTicketState>
implements $GetPrinterTicketStateCopyWith<$Res> {
_$GetPrinterTicketStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'GetPrinterTicketState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PrintModel? printer) success,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements GetPrinterTicketState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'GetPrinterTicketState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PrintModel? printer) success,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements GetPrinterTicketState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$SuccessImplCopyWith<$Res> {
factory _$$SuccessImplCopyWith(
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
__$$SuccessImplCopyWithImpl<$Res>;
@useResult
$Res call({PrintModel? printer});
}
/// @nodoc
class __$$SuccessImplCopyWithImpl<$Res>
extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$SuccessImpl>
implements _$$SuccessImplCopyWith<$Res> {
__$$SuccessImplCopyWithImpl(
_$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? printer = freezed,
}) {
return _then(_$SuccessImpl(
freezed == printer
? _value.printer
: printer // ignore: cast_nullable_to_non_nullable
as PrintModel?,
));
}
}
/// @nodoc
class _$SuccessImpl implements _Success {
const _$SuccessImpl(this.printer);
@override
final PrintModel? printer;
@override
String toString() {
return 'GetPrinterTicketState.success(printer: $printer)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SuccessImpl &&
(identical(other.printer, printer) || other.printer == printer));
}
@override
int get hashCode => Object.hash(runtimeType, printer);
/// Create a copy of GetPrinterTicketState
/// 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(PrintModel? printer) success,
}) {
return success(printer);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) {
return success?.call(printer);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) {
if (success != null) {
return success(printer);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) {
return success(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) {
return success?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) {
if (success != null) {
return success(this);
}
return orElse();
}
}
abstract class _Success implements GetPrinterTicketState {
const factory _Success(final PrintModel? printer) = _$SuccessImpl;
PrintModel? get printer;
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -0,0 +1,6 @@
part of 'get_printer_ticket_bloc.dart';
@freezed
class GetPrinterTicketEvent with _$GetPrinterTicketEvent {
const factory GetPrinterTicketEvent.get() = _Get;
}
@@ -0,0 +1,8 @@
part of 'get_printer_ticket_bloc.dart';
@freezed
class GetPrinterTicketState with _$GetPrinterTicketState {
const factory GetPrinterTicketState.initial() = _Initial;
const factory GetPrinterTicketState.loading() = _Loading;
const factory GetPrinterTicketState.success(PrintModel? printer) = _Success;
}
@@ -0,0 +1,42 @@
import 'package:enaklo_pos/presentation/home/widgets/custom_tab_bar.dart';
import 'package:enaklo_pos/presentation/setting/widgets/bar_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/checker_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/kitchen_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/receipt_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart';
import 'package:enaklo_pos/presentation/setting/widgets/ticket_printer_page.dart';
import 'package:flutter/material.dart';
class SettingPrinterPage extends StatelessWidget {
const SettingPrinterPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
SettingsTitle(
'Printer',
subtitle: 'Kelola printer untuk cetak struk',
),
Expanded(
child: CustomTabBarV2(
tabTitles: [
'Receipt Printer',
'Checker Printer',
'Kitchen Printer',
'Bar Printer',
'Tiket Printer',
],
tabViews: [
ReceiptPrinterPage(),
CheckerPrinterPage(),
KitchenPrinterPage(),
BarPrinterPage(),
TicketPrinterPage()
],
),
),
],
);
}
}
@@ -0,0 +1,112 @@
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/setting/pages/printer_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/setting_tile.dart';
import 'package:flutter/material.dart';
class SettingPage extends StatefulWidget {
const SettingPage({super.key});
@override
State<SettingPage> createState() => _SettingPageState();
}
class _SettingPageState extends State<SettingPage> {
int currentIndex = 0;
String? role;
void indexValue(int index) {
currentIndex = index;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: Row(
children: [
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: Material(
color: AppColors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 12.0,
),
width: double.infinity,
height: context.deviceHeight * 0.1,
decoration: const BoxDecoration(
color: AppColors.white,
border: Border(
bottom: BorderSide(
color: AppColors.background,
width: 1.0,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Pengaturan',
style: TextStyle(
color: AppColors.black,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
Text(
'Kelola pengaturan aplikasi',
style: TextStyle(
color: AppColors.grey,
fontSize: 14,
),
),
],
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
SettingTile(
index: 0,
currentIndex: currentIndex,
title: 'Printer',
subtitle: 'Kelola printer',
icon: Icons.print_outlined,
onTap: () => indexValue(0),
),
],
),
),
),
],
),
),
),
),
Expanded(
flex: 4,
child: Align(
alignment: AlignmentDirectional.topStart,
child: IndexedStack(
index: currentIndex,
children: [
SettingPrinterPage(),
],
),
),
),
],
),
);
}
}
@@ -162,12 +162,17 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
// button test print
Button.outlined(
onPressed: () async {
if (addressController!.text.isNotEmpty && printNameController!.text.isNotEmpty) {
if (addressController!.text.isNotEmpty &&
printNameController!.text.isNotEmpty) {
try {
// Get actual tax and service charge settings
final settingsLocalDatasource = SettingsLocalDatasource();
final taxModel = await settingsLocalDatasource.getTax();
final serviceChargeValue = await settingsLocalDatasource.getServiceCharge();
final settingsLocalDatasource =
SettingsLocalDatasource();
final taxModel =
await settingsLocalDatasource.getTax();
final serviceChargeValue =
await settingsLocalDatasource
.getServiceCharge();
// Create a test print model
final testPrinter = PrintModel(
@@ -179,7 +184,9 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
);
// Generate test print data
final testPrintData = await PrintDataoutputs.instance.printOrderV3(
final testPrintData = await PrintDataoutputs
.instance
.printOrderV3(
[], // Empty product list for test
0,
0,
@@ -206,17 +213,19 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
} catch (e) {
log("Error test printing: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error test printing: $e')),
SnackBar(
content: Text('Error test printing: $e')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please fill in printer details first')),
SnackBar(
content: Text(
'Please fill in printer details first')),
);
}
},
label: 'Test Print'
),
label: 'Test Print'),
SpaceHeight(8),
// button save
data == null
@@ -0,0 +1,310 @@
import 'dart:developer';
import 'package:enaklo_pos/core/function/app_function.dart';
import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/models/response/print_model.dart';
import 'package:enaklo_pos/presentation/setting/bloc/create_printer/create_printer_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/update_printer/update_printer_bloc.dart';
import 'package:enaklo_pos/presentation/setting/widgets/dialog_search_printer.dart';
class TicketPrinterPage extends StatefulWidget {
const TicketPrinterPage({super.key});
@override
State<TicketPrinterPage> createState() => _TicketPrinterPageState();
}
class _TicketPrinterPageState extends State<TicketPrinterPage> {
String selectedPrinter = 'Bluetooth';
TextEditingController? addressController;
TextEditingController? printNameController;
String paper = '58';
bool isInitialized = false;
@override
void initState() {
super.initState();
addressController = TextEditingController();
printNameController = TextEditingController();
context.read<GetPrinterTicketBloc>().add(GetPrinterTicketEvent.get());
}
@override
void dispose() {
addressController!.dispose();
printNameController!.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: BlocBuilder<GetPrinterTicketBloc, GetPrinterTicketState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return SizedBox.shrink();
},
loading: () {
return Center(child: CircularProgressIndicator());
},
success: (data) {
if (data != null && !isInitialized) {
addressController!.text = data.address;
printNameController!.text = data.name;
selectedPrinter = data.type;
paper = data.paper;
isInitialized = true;
}
return Container(
// width: 300,
width: context.deviceWidth,
padding: const EdgeInsets.all(16.0),
color: Colors.white,
child: ListView(
children: [
const Text(
'Tiket Printer',
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
SpaceHeight(16),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Printer Type',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
value: selectedPrinter, // nilai default
items: const [
DropdownMenuItem(
value: 'Bluetooth',
child: Text('Bluetooth'),
),
DropdownMenuItem(
value: 'Network',
child: Text('Network'),
),
],
onChanged: (value) {
selectedPrinter = value ?? 'Bluetooth';
setState(() {});
},
),
SpaceHeight(8),
//button search
selectedPrinter == 'Bluetooth'
? Button.outlined(
onPressed: () {
showDialog(
context: context,
builder: (context) => DialogSearchPrinter(
onSelected: (value) {
addressController!.text = value;
setState(() {});
},
),
);
},
label: 'Search')
: CustomTextField(
controller: addressController!,
label: 'Address',
showLabel: false,
),
SpaceHeight(16),
// Textfield for name
CustomTextField(
controller: printNameController!,
label: 'Print Name',
showLabel: false,
),
SpaceHeight(16),
// Textfield for width paper
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Width Paper',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
value: paper, // nilai default
items: const [
DropdownMenuItem(
value: '58',
child: Text('58 mm'),
),
DropdownMenuItem(
value: '80',
child: Text('80 mm'),
),
],
onChanged: (value) {
paper = value ?? '58';
log('Paper : $paper');
},
),
SpaceHeight(16),
// button test print
Button.outlined(
onPressed: () async {
if (addressController!.text.isNotEmpty &&
printNameController!.text.isNotEmpty) {
try {
// Create a test print model
final testPrinter = PrintModel(
code: 'ticket',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
final barcode =
await generateBarcodeAsUint8List('123');
// Generate test print data
final testPrintData =
await PrintDataoutputs.instance.printTicket(
10000,
barcode,
int.parse(paper),
);
// Print test
await PrinterService().printWithPrinter(
testPrinter,
testPrintData,
context,
);
} catch (e) {
log("Error test printing: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error test printing: $e')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Please fill in printer details first')),
);
}
},
label: 'Test Print'),
SpaceHeight(8),
// button save
data == null
? BlocConsumer<CreatePrinterBloc, CreatePrinterState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: (message) {
final snackBar = SnackBar(
content: Text(message),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
context
.read<GetPrinterTicketBloc>()
.add(GetPrinterTicketEvent.get());
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
final printData = PrintModel(
code: 'ticket',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
context.read<CreatePrinterBloc>().add(
CreatePrinterEvent.createPrinter(
printData),
);
},
label: 'Save',
);
},
loading: () {
return CircularProgressIndicator();
},
);
},
)
: BlocConsumer<UpdatePrinterBloc, UpdatePrinterState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: (message) {
final snackBar = SnackBar(
content: Text(message),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
context
.read<GetPrinterTicketBloc>()
.add(GetPrinterTicketEvent.get());
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
final printData = PrintModel(
id: data.id,
code: 'ticket',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
log("Print Data : ${printData.toMap()}");
context.read<UpdatePrinterBloc>().add(
UpdatePrinterEvent.updatePrinter(
printData,
),
);
},
label: 'Save',
);
},
loading: () {
return CircularProgressIndicator();
},
);
},
),
SpaceHeight(16),
],
),
);
},
);
},
));
}
}
@@ -1,8 +1,11 @@
import 'dart:developer';
import 'package:enaklo_pos/core/components/flushbar.dart';
import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/data/models/response/customer_response_model.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/home/widgets/customer_auto_complete_field.dart';
import 'package:enaklo_pos/presentation/payment/pages/payment_page.dart';
import 'package:flutter/material.dart';
@@ -26,6 +29,9 @@ class SplitBillPage extends StatefulWidget {
}
class _SplitBillPageState extends State<SplitBillPage> {
final customerController = TextEditingController();
Customer? selectedCustomer;
int selectedSplitType = 0; // 0 = Per Product, 1 = Per Amount
// Per Product Split Data
@@ -34,6 +40,7 @@ class _SplitBillPageState extends State<SplitBillPage> {
// Per Amount Split Data
TextEditingController amountController = TextEditingController();
int splitAmount = 0;
int remainingAmount = 0;
List<OrderItem> getOrderItemPending() =>
widget.order.orderItems
@@ -41,9 +48,24 @@ class _SplitBillPageState extends State<SplitBillPage> {
.toList() ??
[];
init() {
setState(() {
remainingAmount =
(widget.order.totalAmount ?? 0) - (widget.order.totalPaid ?? 0);
selectedSplitType = widget.order.splitType == 'AMOUNT' ? 1 : 0;
});
}
@override
void initState() {
init();
super.initState();
}
@override
void dispose() {
amountController.dispose();
super.dispose();
}
@@ -155,6 +177,50 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
SizedBox(height: 8),
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total terbayar',
style: TextStyle(
fontSize: 14,
color: AppColorSplitBill.textPrimary,
fontWeight: FontWeight.w600,
),
),
Text(
'Rp ${_formatCurrency(widget.order.totalPaid ?? 0)}',
style: TextStyle(
fontSize: 14,
color: AppColorSplitBill.textPrimary,
fontWeight: FontWeight.w600,
),
),
],
),
SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Sisa Tagihan',
style: TextStyle(
fontSize: 14,
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
Text(
'Rp ${_formatCurrency(widget.order.remainingAmount ?? 0)}',
style: TextStyle(
fontSize: 14,
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
],
),
SizedBox(height: 2),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -186,6 +252,7 @@ class _SplitBillPageState extends State<SplitBillPage> {
flex: 3,
child: Container(
padding: EdgeInsets.all(24),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -199,19 +266,17 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
SizedBox(height: 24),
_buildCustomer(context),
SizedBox(height: 24),
// Split Type Selection
Row(
children: [
_buildSplitTypeButton('Per Produk', 0),
SizedBox(width: 16),
_buildSplitTypeButton('Per Jumlah', 1),
],
),
_buildRowButtonSplit(),
SizedBox(height: 32),
// Split Content
Expanded(
Container(
child: selectedSplitType == 0
? _buildPerProductSplit()
: _buildPerAmountSplit(),
@@ -247,15 +312,55 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
),
),
),
],
),
);
}
Widget _buildRowButtonSplit() {
switch (widget.order.splitType) {
case 'AMOUNT':
return _buildSplitTypeButton('Per Jumlah', 1);
case 'ITEM':
return _buildSplitTypeButton('Per Produk', 0);
default:
return Row(
children: [
_buildSplitTypeButton('Per Produk', 0),
SizedBox(width: 16),
_buildSplitTypeButton('Per Jumlah', 1),
],
);
}
}
Widget _buildCustomer(BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColorSplitBill.cardBackground,
border: Border.all(color: AppColorSplitBill.border),
borderRadius: BorderRadius.circular(8),
),
child: CustomerAutocomplete(
controller: customerController,
selectedCustomer: selectedCustomer,
onSelected: (customer) {
setState(() {
selectedCustomer = customer;
});
},
),
);
}
Widget _buildOrderItem(OrderItem item) {
return Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
child: Column(
children: [
Row(
children: [
Expanded(
child: Column(
@@ -299,6 +404,59 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
],
),
if ((item.paidQuantity ?? 0) > 1) ...[
SpaceHeight(6),
Align(
alignment: Alignment.centerRight,
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: AppColorSplitBill.primary.withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColorSplitBill.primary),
),
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: '${item.paidQuantity} ',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColorSplitBill.primary,
),
),
TextSpan(
text: 'dari ',
style: const TextStyle(
fontSize: 12,
color: AppColorSplitBill.primary,
),
),
TextSpan(
text: '${item.quantity} ',
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: AppColorSplitBill.primary,
),
),
TextSpan(
text: 'kuantiti telah dibayar.',
style: const TextStyle(
fontSize: 12,
color: AppColorSplitBill.primary,
),
),
],
),
),
),
)
],
],
),
);
}
@@ -347,8 +505,7 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
),
SizedBox(height: 16),
Expanded(
child: Container(
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppColorSplitBill.cardBackground,
@@ -367,15 +524,14 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
),
SizedBox(height: 16),
Expanded(
child: ListView.builder(
ListView.builder(
itemCount: getOrderItemPending().length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return _buildProductSplitItem(
getOrderItemPending()[index]);
return _buildProductSplitItem(getOrderItemPending()[index]);
},
),
),
Container(
width: double.infinity,
height: 1,
@@ -406,14 +562,13 @@ class _SplitBillPageState extends State<SplitBillPage> {
],
),
),
),
],
);
}
Widget _buildProductSplitItem(OrderItem item) {
int selectedQty = selectedProducts[item.id] ?? 0;
int maxQty = item.quantity ?? 0;
int maxQty = (item.quantity ?? 0) - (item.paidQuantity ?? 0);
return Container(
margin: EdgeInsets.only(bottom: 12),
@@ -559,8 +714,7 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
),
SizedBox(height: 16),
Expanded(
child: Column(
Column(
children: [
Container(
padding: EdgeInsets.all(16),
@@ -654,7 +808,7 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
),
Text(
'Rp ${_formatCurrency((widget.order.totalAmount ?? 0) - splitAmount)}',
'Rp ${_formatCurrency((remainingAmount) - splitAmount)}',
style: TextStyle(
fontSize: 14,
color: AppColorSplitBill.textSecondary,
@@ -667,7 +821,6 @@ class _SplitBillPageState extends State<SplitBillPage> {
),
],
),
),
],
);
}
@@ -760,13 +913,19 @@ class _SplitBillPageState extends State<SplitBillPage> {
log("Split Order: ${splitItems.length}");
// Navigate to PaymentPage with the split order
context.push(PaymentPage(order: splitOrder));
context.push(PaymentPage(
order: splitOrder,
isSplit: true,
splitType: 'ITEM',
customerId: selectedCustomer?.id,
));
} else {
AppFlushbar.showError(context, "Pilih minimal satu produk untuk split");
}
} else {
// Per Amount Split
int totalAmount = widget.order.totalAmount ?? 0;
int totalAmount =
(widget.order.totalAmount ?? 0) - (widget.order.totalPaid ?? 0);
if (splitAmount > 0 && splitAmount <= totalAmount) {
// Create split order object with the specified amount
@@ -781,7 +940,14 @@ class _SplitBillPageState extends State<SplitBillPage> {
);
// Navigate to PaymentPage with the split order
context.push(PaymentPage(order: splitOrder));
context.push(
PaymentPage(
order: splitOrder,
isSplit: true,
splitType: 'AMOUNT',
customerId: selectedCustomer?.id,
),
);
} else if (splitAmount > totalAmount) {
AppFlushbar.showError(
context, "Jumlah split tidak boleh melebihi total bill");
@@ -538,7 +538,8 @@ class _SuccessOrderPageState extends State<SuccessOrderPage>
Widget _buildProductCard(int index) {
final item = widget.productQuantity[index];
final totalPrice = (item.product.price ?? 0) * item.quantity;
final totalPrice = (item.product.price ?? 0) * item.quantity +
(item.variant?.priceModifier ?? 0);
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
@@ -634,7 +635,8 @@ class _SuccessOrderPageState extends State<SuccessOrderPage>
borderRadius: BorderRadius.circular(8),
),
child: Text(
(item.product.price ?? 0)
((item.product.price ?? 0) +
(item.variant?.priceModifier ?? 0))
.toString()
.currencyFormatRpV2,
style: TextStyle(
@@ -1005,10 +1007,11 @@ class _SuccessOrderPageState extends State<SuccessOrderPage>
context,
order: widget.order,
paymentMethod: widget.paymentMethod,
nominalBayar: widget.nominalBayar,
nominalBayar: widget.paymentMethod == "Cash"
? widget.nominalBayar
: widget.order.totalAmount ?? 0,
kembalian: widget.nominalBayar -
(widget.order.totalAmount ?? 0),
taxPercentage: widget.taxPercentage,
);
onPrint(
context,
File diff suppressed because it is too large Load Diff
@@ -24,12 +24,27 @@ class SuccessSaveOrderPage extends StatefulWidget {
}
class _SuccessSaveOrderPageState extends State<SuccessSaveOrderPage> {
int totalPrice = 0;
getPrice() {
setState(() {
totalPrice = widget.productQuantity.fold(
0,
(previousValue, element) =>
previousValue +
(element.product.price! * element.quantity) +
(element.variant?.priceModifier ?? 0),
);
});
}
@override
void initState() {
super.initState();
context
.read<OrderLoaderBloc>()
.add(OrderLoaderEvent.getById(widget.orderId));
getPrice();
}
@override
@@ -380,7 +395,8 @@ class _SuccessSaveOrderPageState extends State<SuccessSaveOrderPage> {
Widget _buildProductCard(int index) {
final item = widget.productQuantity[index];
final totalPrice = (item.product.price ?? 0) * item.quantity;
final totalPrice = (item.product.price ?? 0) * item.quantity +
(item.variant?.priceModifier ?? 0);
return Container(
padding: const EdgeInsets.all(16.0),
@@ -464,7 +480,10 @@ class _SuccessSaveOrderPageState extends State<SuccessSaveOrderPage> {
borderRadius: BorderRadius.circular(8),
),
child: Text(
(item.product.price ?? 0).toString().currencyFormatRpV2,
((item.product.price ?? 0) +
(item.variant?.priceModifier ?? 0))
.toString()
.currencyFormatRpV2,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade700,
@@ -707,7 +726,7 @@ class _SuccessSaveOrderPageState extends State<SuccessSaveOrderPage> {
),
),
Text(
(order.totalAmount ?? 0).toString().currencyFormatRpV2,
totalPrice.toString().currencyFormatRpV2,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
@@ -876,7 +895,7 @@ class _SuccessSaveOrderPageState extends State<SuccessSaveOrderPage> {
],
),
Text(
(order.totalAmount ?? 0).toString().currencyFormatRpV2,
totalPrice.toString().currencyFormatRpV2,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
@@ -954,7 +973,7 @@ class _SuccessSaveOrderPageState extends State<SuccessSaveOrderPage> {
],
),
child: Text(
(order.totalAmount ?? 0).toString().currencyFormatRpV2,
totalPrice.toString().currencyFormatRpV2,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
@@ -0,0 +1,26 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'transfer_table_event.dart';
part 'transfer_table_state.dart';
part 'transfer_table_bloc.freezed.dart';
class TransferTableBloc extends Bloc<TransferTableEvent, TransferTableState> {
final TableRemoteDataSource _dataSource;
TransferTableBloc(this._dataSource) : super(TransferTableState.initial()) {
on<_TransferTable>((event, emit) async {
emit(_Loading());
final result = await _dataSource.transferTable(
fromTableId: event.fromTableId,
toTableId: event.toTableId,
);
result.fold(
(l) => emit(_Error(l)),
(r) => emit(_Success()),
);
});
}
}
@@ -0,0 +1,832 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'transfer_table_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$TransferTableEvent {
String get fromTableId => throw _privateConstructorUsedError;
String get toTableId => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String fromTableId, String toTableId)
transferTable,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String fromTableId, String toTableId)? transferTable,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String fromTableId, String toTableId)? transferTable,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_TransferTable value) transferTable,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_TransferTable value)? transferTable,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_TransferTable value)? transferTable,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
/// Create a copy of TransferTableEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TransferTableEventCopyWith<TransferTableEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TransferTableEventCopyWith<$Res> {
factory $TransferTableEventCopyWith(
TransferTableEvent value, $Res Function(TransferTableEvent) then) =
_$TransferTableEventCopyWithImpl<$Res, TransferTableEvent>;
@useResult
$Res call({String fromTableId, String toTableId});
}
/// @nodoc
class _$TransferTableEventCopyWithImpl<$Res, $Val extends TransferTableEvent>
implements $TransferTableEventCopyWith<$Res> {
_$TransferTableEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TransferTableEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? fromTableId = null,
Object? toTableId = null,
}) {
return _then(_value.copyWith(
fromTableId: null == fromTableId
? _value.fromTableId
: fromTableId // ignore: cast_nullable_to_non_nullable
as String,
toTableId: null == toTableId
? _value.toTableId
: toTableId // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$TransferTableImplCopyWith<$Res>
implements $TransferTableEventCopyWith<$Res> {
factory _$$TransferTableImplCopyWith(
_$TransferTableImpl value, $Res Function(_$TransferTableImpl) then) =
__$$TransferTableImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String fromTableId, String toTableId});
}
/// @nodoc
class __$$TransferTableImplCopyWithImpl<$Res>
extends _$TransferTableEventCopyWithImpl<$Res, _$TransferTableImpl>
implements _$$TransferTableImplCopyWith<$Res> {
__$$TransferTableImplCopyWithImpl(
_$TransferTableImpl _value, $Res Function(_$TransferTableImpl) _then)
: super(_value, _then);
/// Create a copy of TransferTableEvent
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? fromTableId = null,
Object? toTableId = null,
}) {
return _then(_$TransferTableImpl(
fromTableId: null == fromTableId
? _value.fromTableId
: fromTableId // ignore: cast_nullable_to_non_nullable
as String,
toTableId: null == toTableId
? _value.toTableId
: toTableId // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$TransferTableImpl implements _TransferTable {
const _$TransferTableImpl(
{required this.fromTableId, required this.toTableId});
@override
final String fromTableId;
@override
final String toTableId;
@override
String toString() {
return 'TransferTableEvent.transferTable(fromTableId: $fromTableId, toTableId: $toTableId)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TransferTableImpl &&
(identical(other.fromTableId, fromTableId) ||
other.fromTableId == fromTableId) &&
(identical(other.toTableId, toTableId) ||
other.toTableId == toTableId));
}
@override
int get hashCode => Object.hash(runtimeType, fromTableId, toTableId);
/// Create a copy of TransferTableEvent
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TransferTableImplCopyWith<_$TransferTableImpl> get copyWith =>
__$$TransferTableImplCopyWithImpl<_$TransferTableImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(String fromTableId, String toTableId)
transferTable,
}) {
return transferTable(fromTableId, toTableId);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String fromTableId, String toTableId)? transferTable,
}) {
return transferTable?.call(fromTableId, toTableId);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String fromTableId, String toTableId)? transferTable,
required TResult orElse(),
}) {
if (transferTable != null) {
return transferTable(fromTableId, toTableId);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_TransferTable value) transferTable,
}) {
return transferTable(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_TransferTable value)? transferTable,
}) {
return transferTable?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_TransferTable value)? transferTable,
required TResult orElse(),
}) {
if (transferTable != null) {
return transferTable(this);
}
return orElse();
}
}
abstract class _TransferTable implements TransferTableEvent {
const factory _TransferTable(
{required final String fromTableId,
required final String toTableId}) = _$TransferTableImpl;
@override
String get fromTableId;
@override
String get toTableId;
/// Create a copy of TransferTableEvent
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TransferTableImplCopyWith<_$TransferTableImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$TransferTableState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(String message)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TransferTableStateCopyWith<$Res> {
factory $TransferTableStateCopyWith(
TransferTableState value, $Res Function(TransferTableState) then) =
_$TransferTableStateCopyWithImpl<$Res, TransferTableState>;
}
/// @nodoc
class _$TransferTableStateCopyWithImpl<$Res, $Val extends TransferTableState>
implements $TransferTableStateCopyWith<$Res> {
_$TransferTableStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TransferTableState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$TransferTableStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of TransferTableState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'TransferTableState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(String message) error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(String message)? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements TransferTableState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$TransferTableStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of TransferTableState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'TransferTableState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(String message) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(String message)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements TransferTableState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$SuccessImplCopyWith<$Res> {
factory _$$SuccessImplCopyWith(
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
__$$SuccessImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$SuccessImplCopyWithImpl<$Res>
extends _$TransferTableStateCopyWithImpl<$Res, _$SuccessImpl>
implements _$$SuccessImplCopyWith<$Res> {
__$$SuccessImplCopyWithImpl(
_$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
: super(_value, _then);
/// Create a copy of TransferTableState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$SuccessImpl implements _Success {
const _$SuccessImpl();
@override
String toString() {
return 'TransferTableState.success()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$SuccessImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(String message) error,
}) {
return success();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(String message)? error,
}) {
return success?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (success != null) {
return success();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return success(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return success?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (success != null) {
return success(this);
}
return orElse();
}
}
abstract class _Success implements TransferTableState {
const factory _Success() = _$SuccessImpl;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value, $Res Function(_$ErrorImpl) then) =
__$$ErrorImplCopyWithImpl<$Res>;
@useResult
$Res call({String message});
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$TransferTableStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of TransferTableState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? message = null,
}) {
return _then(_$ErrorImpl(
null == message
? _value.message
: message // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl(this.message);
@override
final String message;
@override
String toString() {
return 'TransferTableState.error(message: $message)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ErrorImpl &&
(identical(other.message, message) || other.message == message));
}
@override
int get hashCode => Object.hash(runtimeType, message);
/// Create a copy of TransferTableState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
__$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function() success,
required TResult Function(String message) error,
}) {
return error(message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function()? success,
TResult? Function(String message)? error,
}) {
return error?.call(message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function()? success,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(message);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements TransferTableState {
const factory _Error(final String message) = _$ErrorImpl;
String get message;
/// Create a copy of TransferTableState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}
@@ -0,0 +1,8 @@
part of 'transfer_table_bloc.dart';
@freezed
class TransferTableEvent with _$TransferTableEvent {
const factory TransferTableEvent.transferTable(
{required String fromTableId,
required String toTableId}) = _TransferTable;
}
@@ -0,0 +1,9 @@
part of 'transfer_table_bloc.dart';
@freezed
class TransferTableState with _$TransferTableState {
const factory TransferTableState.initial() = _Initial;
const factory TransferTableState.loading() = _Loading;
const factory TransferTableState.success() = _Success;
const factory TransferTableState.error(String message) = _Error;
}
@@ -0,0 +1,319 @@
import 'dart:developer';
import 'package:dropdown_search/dropdown_search.dart';
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';
import 'package:enaklo_pos/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/bloc/get_table_status/get_table_status_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class TransferTableDialog extends StatefulWidget {
final TableModel fromTable;
const TransferTableDialog({
super.key,
required this.fromTable,
});
@override
State<TransferTableDialog> createState() => _TransferTableDialogState();
}
class _TransferTableDialogState extends State<TransferTableDialog> {
TableModel? selectTable;
@override
void initState() {
super.initState();
context
.read<GetTableStatusBloc>()
.add(GetTableStatusEvent.getTablesStatus('available'));
}
@override
Widget build(BuildContext context) {
return BlocListener<TransferTableBloc, TransferTableState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: () {
context.pop();
},
);
},
child: CustomModalDialog(
title: 'Transfer Meja',
subtitle: 'Pilih meja yang tersedia',
contentPadding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
minWidth: context.deviceWidth * 0.4,
minHeight: context.deviceHeight * 0.4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Pilih Meja',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(6.0),
BlocBuilder<GetTableStatusBloc, GetTableStatusState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () =>
Center(child: const CircularProgressIndicator()),
loading: () =>
Center(child: const CircularProgressIndicator()),
success: (tables) {
final availableTables = tables;
if (selectTable == null && availableTables.isNotEmpty) {
selectTable = availableTables.first;
}
if (availableTables.isEmpty) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.orange[50],
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.orange,
width: 1,
),
),
child: const Text(
'Tidak ada meja yang tersedia. Silakan pilih opsi lain.',
style: TextStyle(color: Colors.orange),
),
);
}
return DropdownSearch<TableModel>(
items: tables,
selectedItem: selectTable,
// Dropdown properties
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
hintText: "Pilih meja",
hintStyle: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
prefixIcon: Icon(
Icons.category_outlined,
color: Colors.grey.shade500,
size: 20,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300,
width: 1.5,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300,
width: 1.5,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.blue.shade400,
width: 2,
),
),
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
),
),
// Popup properties
popupProps: PopupProps.menu(
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Cari meja...",
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
),
menuProps: MenuProps(
backgroundColor: Colors.white,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
itemBuilder: (context, category, isSelected) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: BoxDecoration(
color: isSelected
? Colors.blue.shade50
: Colors.transparent,
border: Border(
bottom: BorderSide(
color: Colors.grey.shade100,
width: 0.5,
),
),
),
child: Row(
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: isSelected
? Colors.blue.shade600
: Colors.grey.shade400,
shape: BoxShape.circle,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
category.tableName ?? "",
style: TextStyle(
fontSize: 14,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.w500,
color: isSelected
? Colors.blue.shade700
: Colors.black87,
),
),
),
if (isSelected)
Icon(
Icons.check,
color: Colors.blue.shade600,
size: 18,
),
],
),
);
},
emptyBuilder: (context, searchEntry) {
return Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.search_off,
color: Colors.grey.shade400,
size: 48,
),
const SizedBox(height: 12),
Text(
searchEntry.isEmpty
? "Tidak ada meja tersedia"
: "Tidak ditemukan meja dengan '${searchEntry}'",
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
),
);
},
),
// Item as string (for search functionality)
itemAsString: (TableModel table) =>
table.tableName ?? "",
// Comparison function
compareFn: (TableModel? item1, TableModel? item2) {
return item1?.id == item2?.id;
},
// On changed callback
onChanged: (TableModel? selectedTable) {
if (selectedTable != null) {
setState(() {
selectTable = selectedTable;
});
log("selectTable: ${selectTable!.tableName}");
}
},
// Validator (optional)
validator: (TableModel? value) {
if (value == null) {
return "Meja harus dipilih";
}
return null;
},
);
},
);
}),
],
),
SpaceHeight(24),
BlocBuilder<TransferTableBloc, TransferTableState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => Button.filled(
onPressed: () {
if (selectTable == null) {
AppFlushbar.showError(
context, 'Silahkan Pilih Meja Tujuan');
return;
}
context.read<TransferTableBloc>().add(
TransferTableEvent.transferTable(
fromTableId: widget.fromTable.id ?? "",
toTableId: selectTable!.id ?? "",
),
);
},
label: "Transfer",
),
loading: () => Center(
child: const CircularProgressIndicator(),
),
);
},
),
],
),
),
);
}
}
+81 -24
View File
@@ -1,3 +1,4 @@
import 'package:enaklo_pos/core/components/flushbar.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';
@@ -5,7 +6,9 @@ import 'package:enaklo_pos/presentation/home/pages/dashboard_page.dart';
import 'package:enaklo_pos/presentation/table/blocs/change_position_table/change_position_table_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/create_table/create_table_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/get_table/get_table_bloc.dart';
import 'package:enaklo_pos/presentation/table/blocs/transfer_table/transfer_table_bloc.dart';
import 'package:enaklo_pos/presentation/table/dialogs/form_table_new_dialog.dart';
import 'package:enaklo_pos/presentation/table/dialogs/transfer_table_dialog.dart';
import 'package:enaklo_pos/presentation/table/widgets/table_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -41,8 +44,11 @@ class _TablePageState extends State<TablePage> {
// Untuk drag
TableModel? draggingTable;
Offset? _tapPosition;
// Ubah function toggleSelectTable menjadi selectTable
void selectTable(TableModel table) {
if (table.status == 'occupied') return;
setState(() {
if (selectedTable == table) {
selectedTable = null; // Deselect jika table yang sama diklik
@@ -111,10 +117,24 @@ class _TablePageState extends State<TablePage> {
),
),
backgroundColor: const Color(0xFFF7F8FA),
body: BlocBuilder<GetTableBloc, GetTableState>(
body: BlocListener<TransferTableBloc, TransferTableState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: () {
AppFlushbar.showSuccess(context, 'Transfer Table Success');
context.read<GetTableBloc>().add(const GetTableEvent.getTables());
},
error: (message) {
AppFlushbar.showError(context, message);
},
);
},
child: BlocBuilder<GetTableBloc, GetTableState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => SizedBox.shrink(),
loading: () => const Center(child: CircularProgressIndicator()),
success: (tables) => SafeArea(
child: Stack(
children: [
@@ -191,7 +211,8 @@ class _TablePageState extends State<TablePage> {
draggingTable = table;
});
},
onDraggableCanceled: (velocity, offset) {
onDraggableCanceled:
(velocity, offset) {
setState(() {
draggingTable = null;
});
@@ -199,26 +220,36 @@ class _TablePageState extends State<TablePage> {
onDragEnd: (details) {
setState(() {
draggingTable = null;
final RenderBox box = context
.findRenderObject() as RenderBox;
final Offset local =
box.globalToLocal(details.offset);
table.positionX =
local.dx.clamp(0, mapWidth - 120);
table.positionY =
local.dy.clamp(0, mapHeight - 80);
final RenderBox box =
context.findRenderObject()
as RenderBox;
final Offset local = box
.globalToLocal(details.offset);
table.positionX = local.dx
.clamp(0, mapWidth - 120);
table.positionY = local.dy
.clamp(0, mapHeight - 80);
});
context
.read<ChangePositionTableBloc>()
.add(ChangePositionTableEvent
.changePositionTable(
tableId: table.id ?? "",
position: details.offset,
position: Offset(
table.positionX ?? 0.0,
table.positionY ?? 0.0,
),
));
});
},
child: GestureDetector(
onTap: () => selectTable(table),
onTapDown: (details) => _tapPosition =
details.globalPosition,
onLongPress: () {
if (table.status == 'occupied') {
_showPopupMenu(context, table);
}
},
child: TableWidget(
table: table,
isSelected: isSelected,
@@ -244,6 +275,7 @@ class _TablePageState extends State<TablePage> {
);
},
),
),
);
}
@@ -362,17 +394,7 @@ class _TablePageState extends State<TablePage> {
context.push(DashboardPage(
table: selectedTable!,
));
} else {
// Handle occupied table click - load draft order and navigate to payment
// context.read<CheckoutBloc>().add(
// CheckoutEvent.loadDraftOrder(data!),
// );
// log("Data Draft Order: ${data!.toMap()}");
// context.push(PaymentTablePage(
// table: widget.table,
// draftOrder: data!,
// ));
}
} else {}
},
child: const Text(
"Tempatkan Pesanan",
@@ -397,4 +419,39 @@ class _TablePageState extends State<TablePage> {
],
);
}
void _showPopupMenu(BuildContext context, TableModel table) {
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showMenu(
context: context,
position: RelativeRect.fromRect(
_tapPosition != null
? Rect.fromLTWH(_tapPosition!.dx, _tapPosition!.dy, 0, 0)
: Rect.fromLTWH(100, 100, 0, 0),
Offset.zero & overlay.size,
),
color: AppColors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
elevation: 1,
items: [
PopupMenuItem(
onTap: () {
showDialog(
context: context,
builder: (context) => TransferTableDialog(fromTable: table),
);
},
child: Row(
children: [
Icon(Icons.swap_horiz),
SizedBox(width: 8),
Text('Transfer'),
],
),
),
],
);
}
}

Some files were not shown because too many files have changed in this diff Show More