Compare commits

..

No commits in common. "7678b4791db2422f781bd8f44b5a8966c290a099" and "bd7e925306411e62e30d719220d8dc981f04690a" have entirely different histories.

34 changed files with 1063 additions and 2114 deletions

View File

@ -2,5 +2,5 @@ class Variables {
static const String appName = 'POS Kasir Resto App'; static const String appName = 'POS Kasir Resto App';
static const String apiVersion = 'v1'; static const String apiVersion = 'v1';
// static const String baseUrl = 'http://192.168.1.202:8000'; // static const String baseUrl = 'http://192.168.1.202:8000';
static const String baseUrl = 'https://enaklo-pos-be.altru.id'; static const String baseUrl = 'https://pos-app-tablet.enaklo.co.id';
} }

View File

@ -1,22 +0,0 @@
// lib/core/network/dio_client.dart
import 'package:awesome_dio_interceptor/awesome_dio_interceptor.dart';
import 'package:dio/dio.dart';
class DioClient {
static final Dio _dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
headers: {
'Accept': 'application/json',
},
))
..interceptors.add(
AwesomeDioInterceptor(
logRequestTimeout: true,
logRequestHeaders: true,
logResponseHeaders: true,
),
);
static Dio get instance => _dio;
}

View File

@ -60,12 +60,12 @@ class PrintDataoutputs {
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: text:
'${product.product.price!.currencyFormatRp} x ${product.quantity}', '${product.product.price!.toIntegerFromText.currencyFormatRp} x ${product.quantity}',
width: 8, width: 8,
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: '${product.product.price! * product.quantity}' text: '${product.product.price!.toIntegerFromText * product.quantity}'
.toIntegerFromText .toIntegerFromText
.currencyFormatRp, .currencyFormatRp,
width: 4, width: 4,
@ -328,7 +328,8 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: (product.product.price! * product.quantity).currencyFormatRp, text: (product.product.price!.toIntegerFromText * product.quantity)
.currencyFormatRp,
width: 4, width: 4,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
), ),
@ -397,7 +398,8 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: (products[0].product.price! * products[0].quantity) text: (products[0].product.price!.toIntegerFromText *
products[0].quantity)
.currencyFormatRp, .currencyFormatRp,
width: 4, width: 4,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
@ -428,7 +430,8 @@ class PrintDataoutputs {
styles: const PosStyles(align: PosAlign.left), styles: const PosStyles(align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: (products[0].product.price! * products[0].quantity) text: (products[0].product.price!.toIntegerFromText *
products[0].quantity)
.currencyFormatRp, .currencyFormatRp,
width: 4, width: 4,
styles: const PosStyles(align: PosAlign.right), styles: const PosStyles(align: PosAlign.right),
@ -607,8 +610,8 @@ class PrintDataoutputs {
styles: const PosStyles(bold: true, align: PosAlign.left), styles: const PosStyles(bold: true, align: PosAlign.left),
), ),
PosColumn( PosColumn(
text: text: '${product.product.price!.toIntegerFromText * product.quantity}'
'${product.product.price! * product.quantity}'.currencyFormatRpV2, .currencyFormatRpV2,
width: 4, width: 4,
styles: const PosStyles(bold: true, align: PosAlign.right), styles: const PosStyles(bold: true, align: PosAlign.right),
), ),
@ -623,7 +626,8 @@ class PrintDataoutputs {
final subTotalPrice = products.fold<int>( final subTotalPrice = products.fold<int>(
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + (element.product.price! * element.quantity)); previousValue +
(element.product.price!.toIntegerFromText * element.quantity));
bytes += generator.row([ bytes += generator.row([
PosColumn( PosColumn(
text: 'Subtotal $totalQuantity Product', text: 'Subtotal $totalQuantity Product',
@ -991,27 +995,27 @@ class PrintDataoutputs {
: '--------------------------------', : '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center)); styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.feed(1); bytes += generator.feed(1);
// final kitchenProducts = final kitchenProducts =
// products.where((p) => p.product.printerType == 'kitchen'); products.where((p) => p.product.printerType == 'kitchen');
// for (final product in kitchenProducts) { for (final product in kitchenProducts) {
// bytes += generator.text('${product.quantity} x ${product.product.name}', bytes += generator.text('${product.quantity} x ${product.product.name}',
// styles: const PosStyles( styles: const PosStyles(
// align: PosAlign.left, align: PosAlign.left,
// bold: false, bold: false,
// height: PosTextSize.size2, height: PosTextSize.size2,
// width: PosTextSize.size1, width: PosTextSize.size1,
// )); ));
// if (product.notes.isNotEmpty) { if (product.notes.isNotEmpty) {
// bytes += generator.text(' Notes: ${product.notes}', bytes += generator.text(' Notes: ${product.notes}',
// styles: const PosStyles( styles: const PosStyles(
// align: PosAlign.left, align: PosAlign.left,
// bold: false, bold: false,
// height: PosTextSize.size1, height: PosTextSize.size1,
// width: PosTextSize.size1, width: PosTextSize.size1,
// fontType: PosFontType.fontA, fontType: PosFontType.fontA,
// )); ));
// } }
// } }
bytes += generator.feed(1); bytes += generator.feed(1);
bytes += generator.text( bytes += generator.text(
@ -1105,26 +1109,26 @@ class PrintDataoutputs {
: '--------------------------------', : '--------------------------------',
styles: const PosStyles(bold: false, align: PosAlign.center)); styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.feed(1); bytes += generator.feed(1);
// final barProducts = products.where((p) => p.product.printerType == 'bar'); final barProducts = products.where((p) => p.product.printerType == 'bar');
// for (final product in barProducts) { for (final product in barProducts) {
// bytes += generator.text('${product.quantity} x ${product.product.name}', bytes += generator.text('${product.quantity} x ${product.product.name}',
// styles: const PosStyles( styles: const PosStyles(
// align: PosAlign.left, align: PosAlign.left,
// bold: false, bold: false,
// height: PosTextSize.size2, height: PosTextSize.size2,
// width: PosTextSize.size1, width: PosTextSize.size1,
// )); ));
// if (product.notes.isNotEmpty) { if (product.notes.isNotEmpty) {
// bytes += generator.text(' Notes: ${product.notes}', bytes += generator.text(' Notes: ${product.notes}',
// styles: const PosStyles( styles: const PosStyles(
// align: PosAlign.left, align: PosAlign.left,
// bold: false, bold: false,
// height: PosTextSize.size1, height: PosTextSize.size1,
// width: PosTextSize.size1, width: PosTextSize.size1,
// fontType: PosFontType.fontA, fontType: PosFontType.fontA,
// )); ));
// } }
// } }
bytes += generator.feed(1); bytes += generator.feed(1);
bytes += generator.text( bytes += generator.text(

View File

@ -1,67 +1,44 @@
import 'dart:developer';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/constants/variables.dart'; import 'package:enaklo_pos/core/constants/variables.dart';
import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/auth_response_model.dart'; import 'package:enaklo_pos/data/models/response/auth_response_model.dart';
import 'package:http/http.dart' as http;
class AuthRemoteDatasource { class AuthRemoteDatasource {
final Dio dio = DioClient.instance;
Future<Either<String, AuthResponseModel>> login( Future<Either<String, AuthResponseModel>> login(
String email, String password) async { String email, String password) async {
final url = '${Variables.baseUrl}/api/v1/auth/login'; final url = Uri.parse('${Variables.baseUrl}/api/login');
log(url); final response = await http.post(
url,
body: {
'email': email,
'password': password,
},
);
try { if (response.statusCode == 200) {
final response = await dio.post( return Right(AuthResponseModel.fromJson(response.body));
url, } else {
data: { return const Left('Failed to login');
'email': email,
'password': password,
},
);
if (response.statusCode == 200) {
return Right(AuthResponseModel.fromMap(response.data));
} else {
return const Left('Failed to login');
}
} on DioException catch (e) {
log("Dio error: ${e.message}");
return Left(e.response?.data['message'] ?? 'Login gagal');
} catch (e) {
log("Unexpected error: $e");
return const Left('Unexpected error occurred');
} }
} }
//logout //logout
Future<Either<String, bool>> logout() async { Future<Either<String, bool>> logout() async {
try { final authData = await AuthLocalDataSource().getAuthData();
final authData = await AuthLocalDataSource().getAuthData(); final url = Uri.parse('${Variables.baseUrl}/api/logout');
final url = '${Variables.baseUrl}/api/v1/auth/logout'; final response = await http.post(
url,
headers: {
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
},
);
final response = await dio.post( if (response.statusCode == 200) {
url, return const Right(true);
options: Options( } else {
headers: { return const Left('Failed to logout');
'Authorization': 'Bearer ${authData.token}',
'Accept': 'application/json',
},
),
);
if (response.statusCode == 200) {
return const Right(true);
} else {
return const Left('Failed to logout');
}
} on DioException catch (e) {
return Left(e.response?.data['message'] ?? 'Logout gagal');
} catch (e) {
return const Left('Unexpected error occurred');
} }
} }
} }

View File

@ -308,7 +308,7 @@ class ProductLocalDatasource {
tableProduct, tableProduct,
product.toLocalMap(), product.toLocalMap(),
where: 'product_id = ?', where: 'product_id = ?',
whereArgs: [product.id], whereArgs: [product.productId],
); );
} }
@ -319,7 +319,7 @@ class ProductLocalDatasource {
for (var product in products) { for (var product in products) {
await db.insert(tableProduct, product.toLocalMap(), await db.insert(tableProduct, product.toLocalMap(),
conflictAlgorithm: ConflictAlgorithm.replace); conflictAlgorithm: ConflictAlgorithm.replace);
log('inserted success id: ${product.id} | name: ${product.name} | price: ${product.price} '); log('inserted success id: ${product.productId} | name: ${product.name} | price: ${product.price} | Printer Type ${product.printerType}');
} }
} }

View File

@ -1,8 +1,6 @@
import 'dart:developer'; import 'dart:developer';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:enaklo_pos/core/network/dio_client.dart';
import 'package:enaklo_pos/data/models/request/product_request_model.dart'; import 'package:enaklo_pos/data/models/request/product_request_model.dart';
import 'package:enaklo_pos/data/models/response/add_product_response_model.dart'; import 'package:enaklo_pos/data/models/response/add_product_response_model.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart';
@ -12,34 +10,19 @@ import '../../core/constants/variables.dart';
import 'auth_local_datasource.dart'; import 'auth_local_datasource.dart';
class ProductRemoteDatasource { class ProductRemoteDatasource {
final Dio dio = DioClient.instance;
Future<Either<String, ProductResponseModel>> getProducts() async { Future<Either<String, ProductResponseModel>> getProducts() async {
try { final url = Uri.parse('${Variables.baseUrl}/api/products');
final authData = await AuthLocalDataSource().getAuthData(); final authData = await AuthLocalDataSource().getAuthData();
final url = '${Variables.baseUrl}/api/v1/products'; final response = await http.get(url, headers: {
'Authorization': 'Bearer ${authData.token}',
final response = await dio.get( 'Accept': 'application/json',
url, });
options: Options( log("Status Code: ${response.statusCode}");
headers: { log("Body: ${response.body}");
'Authorization': 'Bearer ${authData.token}', if (response.statusCode == 200) {
'Accept': 'application/json', return Right(ProductResponseModel.fromJson(response.body));
}, } else {
), return const Left('Failed to get products');
);
if (response.statusCode == 200) {
return Right(ProductResponseModel.fromMap(response.data));
} else {
return const Left('Failed to get products');
}
} on DioException catch (e) {
log("Dio error: ${e.message}");
return Left(e.response?.data['message'] ?? 'Gagal mengambil produk');
} catch (e) {
log("Unexpected error: $e");
return const Left('Unexpected error occurred');
} }
} }
@ -74,7 +57,7 @@ class ProductRemoteDatasource {
final Map<String, String> headers = { final Map<String, String> headers = {
'Authorization': 'Bearer ${authData.token}', 'Authorization': 'Bearer ${authData.token}',
}; };
log("Update Product Request Data: ${productRequestModel.toMap()}"); log("Update Product Request Data: ${productRequestModel.toMap()}");
log("Update Product ID: ${productRequestModel.id}"); log("Update Product ID: ${productRequestModel.id}");
log("Update Product Name: ${productRequestModel.name}"); log("Update Product Name: ${productRequestModel.name}");
@ -84,7 +67,7 @@ class ProductRemoteDatasource {
log("Update Product Is Best Seller: ${productRequestModel.isBestSeller}"); log("Update Product Is Best Seller: ${productRequestModel.isBestSeller}");
log("Update Product Printer Type: ${productRequestModel.printerType}"); log("Update Product Printer Type: ${productRequestModel.printerType}");
log("Update Product Has Image: ${productRequestModel.image != null}"); log("Update Product Has Image: ${productRequestModel.image != null}");
var request = http.MultipartRequest( var request = http.MultipartRequest(
'POST', Uri.parse('${Variables.baseUrl}/api/products/edit')); 'POST', Uri.parse('${Variables.baseUrl}/api/products/edit'));
request.fields.addAll(productRequestModel.toMap()); request.fields.addAll(productRequestModel.toMap());

View File

@ -3,7 +3,7 @@ import 'dart:developer';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
class ProductRequestModel { class ProductRequestModel {
final String? id; final int? id;
final String name; final String name;
final int price; final int price;
final int stock; final int stock;
@ -32,11 +32,11 @@ class ProductRequestModel {
'is_best_seller': isBestSeller.toString(), 'is_best_seller': isBestSeller.toString(),
'printer_type': printerType ?? '', 'printer_type': printerType ?? '',
}; };
if (id != null) { if (id != null) {
map['id'] = id.toString(); map['id'] = id.toString();
} }
return map; return map;
} }
} }

View File

@ -1,83 +1,85 @@
import 'dart:convert'; import 'dart:convert';
class AuthResponseModel { class AuthResponseModel {
final String? token; final String? status;
final User? user; final String? token;
final User? user;
AuthResponseModel({ AuthResponseModel({
this.token, this.status,
this.user, this.token,
}); this.user,
});
factory AuthResponseModel.fromJson(String str) => factory AuthResponseModel.fromJson(String str) => AuthResponseModel.fromMap(json.decode(str));
AuthResponseModel.fromMap(json.decode(str));
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());
factory AuthResponseModel.fromMap(Map<String, dynamic> json) => factory AuthResponseModel.fromMap(Map<String, dynamic> json) => AuthResponseModel(
AuthResponseModel( status: json["status"],
token: json["token"], token: json["token"],
user: json["user"] == null ? null : User.fromMap(json["user"]), user: json["user"] == null ? null : User.fromMap(json["user"]),
); );
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
"status": status,
"token": token, "token": token,
"user": user?.toMap(), "user": user?.toMap(),
}; };
} }
class User { class User {
final String? id; final int? id;
final String? organizationId; final String? name;
final String? outletId; final String? email;
final String? name; final DateTime? emailVerifiedAt;
final String? email; final dynamic twoFactorSecret;
final String? role; final dynamic twoFactorRecoveryCodes;
final bool? isActive; final dynamic twoFactorConfirmedAt;
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
final String? role;
User({ User({
this.id, this.id,
this.organizationId, this.name,
this.outletId, this.email,
this.name, this.emailVerifiedAt,
this.role, this.twoFactorSecret,
this.isActive, this.twoFactorRecoveryCodes,
this.email, this.twoFactorConfirmedAt,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
}); this.role,
});
factory User.fromJson(String str) => User.fromMap(json.decode(str)); factory User.fromJson(String str) => User.fromMap(json.decode(str));
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());
factory User.fromMap(Map<String, dynamic> json) => User( factory User.fromMap(Map<String, dynamic> json) => User(
id: json["id"], id: json["id"],
organizationId: json["organization_id"],
outletId: json["outlet_id"],
name: json["name"], name: json["name"],
email: json["email"], email: json["email"],
emailVerifiedAt: json["email_verified_at"] == null ? null : DateTime.parse(json["email_verified_at"]),
twoFactorSecret: json["two_factor_secret"],
twoFactorRecoveryCodes: json["two_factor_recovery_codes"],
twoFactorConfirmedAt: json["two_factor_confirmed_at"],
createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null ? null : DateTime.parse(json["updated_at"]),
role: json["role"], role: json["role"],
isActive: json["is_active"], );
createdAt: json["created_at"] == null
? null
: DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null
? null
: DateTime.parse(json["updated_at"]),
);
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
"id": id, "id": id,
"organization_id": organizationId,
"outlet_id": outletId,
"name": name, "name": name,
"email": email, "email": email,
"role": role, "email_verified_at": emailVerifiedAt?.toIso8601String(),
"is_active": isActive, "two_factor_secret": twoFactorSecret,
"two_factor_recovery_codes": twoFactorRecoveryCodes,
"two_factor_confirmed_at": twoFactorConfirmedAt,
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
}; "role": role,
};
} }

View File

@ -1,15 +1,17 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart';
class ProductResponseModel { class ProductResponseModel {
final bool? success; final String? status;
final ProductData? data; final List<Product>? data;
final dynamic errors;
ProductResponseModel({ ProductResponseModel({
this.success, this.status,
this.data, this.data,
this.errors,
}); });
factory ProductResponseModel.fromJson(String str) => factory ProductResponseModel.fromJson(String str) =>
@ -19,86 +21,50 @@ class ProductResponseModel {
factory ProductResponseModel.fromMap(Map<String, dynamic> json) => factory ProductResponseModel.fromMap(Map<String, dynamic> json) =>
ProductResponseModel( ProductResponseModel(
success: json["success"], status: json["status"],
data: json["data"] == null ? null : ProductData.fromMap(json["data"]), data: json["data"] == null
errors: json["errors"], ? []
: List<Product>.from(json["data"]!.map((x) => Product.fromMap(x))),
); );
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
"success": success, "status": status,
"data": data?.toMap(), "data":
"errors": errors, data == null ? [] : List<dynamic>.from(data!.map((x) => x.toMap())),
};
}
class ProductData {
final List<Product>? products;
final int? totalCount;
final int? page;
final int? limit;
final int? totalPages;
ProductData({
this.products,
this.totalCount,
this.page,
this.limit,
this.totalPages,
});
factory ProductData.fromMap(Map<String, dynamic> json) => ProductData(
products: json["products"] == null
? []
: List<Product>.from(
json["products"].map((x) => Product.fromMap(x))),
totalCount: json["total_count"],
page: json["page"],
limit: json["limit"],
totalPages: json["total_pages"],
);
Map<String, dynamic> toMap() => {
"products": products == null
? []
: List<dynamic>.from(products!.map((x) => x.toMap())),
"total_count": totalCount,
"page": page,
"limit": limit,
"total_pages": totalPages,
}; };
} }
class Product { class Product {
final String? id; final int? id;
final String? organizationId; final int? productId;
final String? categoryId; final int? categoryId;
final String? sku;
final String? name; final String? name;
final String? description; final String? description;
final int? price; final String? image;
final int? cost; final String? price;
final String? businessType; final int? stock;
final Map<String, dynamic>? metadata; final int? status;
final bool? isActive; final int? isFavorite;
final DateTime? createdAt; final DateTime? createdAt;
final DateTime? updatedAt; final DateTime? updatedAt;
final List<ProductVariant>? variants; final Category? category;
final String? printerType;
Product({ Product({
this.id, this.id,
this.organizationId, this.productId,
this.categoryId, this.categoryId,
this.sku,
this.name, this.name,
this.description, this.description,
this.image,
this.price, this.price,
this.cost, this.stock,
this.businessType, this.status,
this.metadata, this.isFavorite,
this.isActive,
this.createdAt, this.createdAt,
this.updatedAt, this.updatedAt,
this.variants, this.category,
this.printerType,
}); });
factory Product.fromJson(String str) => Product.fromMap(json.decode(str)); factory Product.fromJson(String str) => Product.fromMap(json.decode(str));
@ -106,94 +72,91 @@ class Product {
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());
factory Product.fromMap(Map<String, dynamic> json) => Product( factory Product.fromMap(Map<String, dynamic> json) => Product(
id: json["id"], id: json["id"] is String ? int.tryParse(json["id"]) : json["id"],
organizationId: json["organization_id"], productId: json["product_id"] is String ? int.tryParse(json["product_id"]) : json["product_id"],
categoryId: json["category_id"], categoryId: json["category_id"] is String
sku: json["sku"], ? int.tryParse(json["category_id"])
: json["category_id"],
name: json["name"], name: json["name"],
description: json["description"], description: json["description"],
price: json["price"], image: json["image"],
cost: json["cost"], // price: json["price"].substring(0, json["price"].length - 3),
businessType: json["business_type"], price: json["price"].toString().replaceAll('.00', ''),
metadata: json["metadata"] ?? {}, stock: json["stock"] is String ? int.tryParse(json["stock"]) : json["stock"],
isActive: json["is_active"], status: json["status"] is String ? int.tryParse(json["status"]) : json["status"],
isFavorite: json["is_favorite"] is String ? int.tryParse(json["is_favorite"]) : json["is_favorite"],
createdAt: json["created_at"] == null createdAt: json["created_at"] == null
? null ? null
: DateTime.parse(json["created_at"]), : DateTime.parse(json["created_at"]),
updatedAt: json["updated_at"] == null updatedAt: json["updated_at"] == null
? null ? null
: DateTime.parse(json["updated_at"]), : DateTime.parse(json["updated_at"]),
variants: json["variants"] == null category: json["category"] == null
? [] ? null
: List<ProductVariant>.from( : Category.fromMap(json["category"]),
json["variants"].map((x) => ProductVariant.fromMap(x))), printerType: json["printer_type"] ?? 'bar',
); );
factory Product.fromOrderMap(Map<String, dynamic> json) => Product( factory Product.fromOrderMap(Map<String, dynamic> json) => Product(
id: json["id_product"], id: json["id_product"],
price: json["price"], price: json["price"].toString(),
); );
factory Product.fromLocalMap(Map<String, dynamic> json) => Product( factory Product.fromLocalMap(Map<String, dynamic> json) => Product(
id: json["id"], id: json["id"],
organizationId: json["organization_id"], productId: json["product_id"],
categoryId: json["category_id"], categoryId: json["categoryId"],
sku: json["sku"], category: Category(
id: json["categoryId"],
name: json["categoryName"],
),
name: json["name"], name: json["name"],
description: json["description"], description: json["description"],
image: json["image"],
price: json["price"], price: json["price"],
cost: json["cost"], stock: json["stock"],
businessType: json["business_type"], status: json["status"],
metadata: json["metadata"] ?? {}, isFavorite: json["isFavorite"],
isActive: json["is_active"], createdAt: json["createdAt"] == null
createdAt: json["created_at"] == null
? null ? null
: DateTime.parse(json["created_at"]), : DateTime.parse(json["createdAt"]),
updatedAt: json["updated_at"] == null updatedAt: json["updatedAt"] == null
? null ? null
: DateTime.parse(json["updated_at"]), : DateTime.parse(json["updatedAt"]),
variants: json["variants"] == null printerType: json["printer_type"] ?? 'bar',
? []
: List<ProductVariant>.from(
json["variants"].map((x) => ProductVariant.fromMap(x))),
); );
Map<String, dynamic> toLocalMap() => { Map<String, dynamic> toLocalMap() => {
"id": id, "product_id": id,
"organization_id": organizationId, "categoryId": categoryId,
"category_id": categoryId, "categoryName": category?.name,
"sku": sku,
"name": name, "name": name,
"description": description, "description": description,
"price": price, "image": image,
"cost": cost, "price": price?.replaceAll(RegExp(r'\.0+$'), ''),
"business_type": businessType, "stock": stock,
"metadata": metadata, "status": status,
"is_active": isActive, "isFavorite": isFavorite,
"created_at": createdAt?.toIso8601String(), "createdAt": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updatedAt": updatedAt?.toIso8601String(),
"variants": variants == null "printer_type": printerType,
? []
: List<dynamic>.from(variants!.map((x) => x.toMap())),
}; };
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
"id": id, "id": id,
"organization_id": organizationId, "product_id": productId,
"category_id": categoryId, "category_id": categoryId,
"sku": sku,
"name": name, "name": name,
"description": description, "description": description,
"image": image,
"price": price, "price": price,
"cost": cost, "stock": stock,
"business_type": businessType, "status": status,
"metadata": metadata, "is_favorite": isFavorite,
"is_active": isActive,
"created_at": createdAt?.toIso8601String(), "created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(), "updated_at": updatedAt?.toIso8601String(),
"variants": variants == null "category": category?.toMap(),
? [] "printer_type": printerType,
: List<dynamic>.from(variants!.map((x) => x.toMap())),
}; };
@override @override
@ -201,80 +164,70 @@ class Product {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other.id == id && return other.id == id &&
other.organizationId == organizationId && other.productId == productId &&
other.categoryId == categoryId && other.categoryId == categoryId &&
other.sku == sku &&
other.name == name && other.name == name &&
other.description == description && other.description == description &&
other.image == image &&
other.price == price && other.price == price &&
other.cost == cost && other.stock == stock &&
other.businessType == businessType && other.status == status &&
other.metadata == metadata && other.isFavorite == isFavorite &&
other.isActive == isActive &&
other.createdAt == createdAt && other.createdAt == createdAt &&
other.updatedAt == updatedAt && other.updatedAt == updatedAt &&
_listEquals(other.variants, variants); other.category == category &&
} other.printerType == printerType;
bool _listEquals(List<ProductVariant>? a, List<ProductVariant>? b) {
if (a == null && b == null) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
} }
@override @override
int get hashCode { int get hashCode {
return id.hashCode ^ return id.hashCode ^
organizationId.hashCode ^ productId.hashCode ^
categoryId.hashCode ^ categoryId.hashCode ^
sku.hashCode ^
name.hashCode ^ name.hashCode ^
description.hashCode ^ description.hashCode ^
image.hashCode ^
price.hashCode ^ price.hashCode ^
cost.hashCode ^ stock.hashCode ^
businessType.hashCode ^ status.hashCode ^
metadata.hashCode ^ isFavorite.hashCode ^
isActive.hashCode ^
createdAt.hashCode ^ createdAt.hashCode ^
updatedAt.hashCode ^ updatedAt.hashCode ^
variants.hashCode; category.hashCode ^
printerType.hashCode;
} }
Product copyWith({ Product copyWith({
String? id, int? id,
String? organizationId, int? productId,
String? categoryId, int? categoryId,
String? sku,
String? name, String? name,
String? description, String? description,
int? price, String? image,
int? cost, String? price,
String? businessType, int? stock,
Map<String, dynamic>? metadata, int? status,
bool? isActive, int? isFavorite,
DateTime? createdAt, DateTime? createdAt,
DateTime? updatedAt, DateTime? updatedAt,
List<ProductVariant>? variants, Category? category,
String? printerType,
}) { }) {
return Product( return Product(
id: id ?? this.id, id: id ?? this.id,
organizationId: organizationId ?? this.organizationId, productId: productId ?? this.productId,
categoryId: categoryId ?? this.categoryId, categoryId: categoryId ?? this.categoryId,
sku: sku ?? this.sku,
name: name ?? this.name, name: name ?? this.name,
description: description ?? this.description, description: description ?? this.description,
image: image ?? this.image,
price: price ?? this.price, price: price ?? this.price,
cost: cost ?? this.cost, stock: stock ?? this.stock,
businessType: businessType ?? this.businessType, status: status ?? this.status,
metadata: metadata ?? this.metadata, isFavorite: isFavorite ?? this.isFavorite,
isActive: isActive ?? this.isActive,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt, updatedAt: updatedAt ?? this.updatedAt,
variants: variants ?? this.variants, category: category ?? this.category,
printerType: printerType ?? this.printerType,
); );
} }
} }
@ -344,51 +297,3 @@ class Category {
updatedAt.hashCode; updatedAt.hashCode;
} }
} }
class ProductVariant {
final String? id;
final String? productId;
final String? name;
final int? priceModifier;
final int? cost;
final Map<String, dynamic>? metadata;
final DateTime? createdAt;
final DateTime? updatedAt;
ProductVariant({
this.id,
this.productId,
this.name,
this.priceModifier,
this.cost,
this.metadata,
this.createdAt,
this.updatedAt,
});
factory ProductVariant.fromMap(Map<String, dynamic> json) => ProductVariant(
id: json["id"],
productId: json["product_id"],
name: json["name"],
priceModifier: json["price_modifier"],
cost: json["cost"],
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"]),
);
Map<String, dynamic> toMap() => {
"id": id,
"product_id": productId,
"name": name,
"price_modifier": priceModifier,
"cost": cost,
"metadata": metadata,
"created_at": createdAt?.toIso8601String(),
"updated_at": updatedAt?.toIso8601String(),
};
}

View File

@ -1,6 +1,5 @@
import 'dart:developer'; import 'dart:developer';
import 'package:enaklo_pos/core/constants/theme.dart'; import 'package:enaklo_pos/core/constants/theme.dart';
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/auth_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_remote_datasource.dart';
@ -218,9 +217,6 @@ class _MyAppState extends State<MyApp> {
BlocProvider( BlocProvider(
create: (context) => AddOrderItemsBloc(OrderRemoteDatasource()), create: (context) => AddOrderItemsBloc(OrderRemoteDatasource()),
), ),
BlocProvider(
create: (context) => ProductLoaderBloc(ProductRemoteDatasource()),
),
], ],
child: MaterialApp( child: MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,

View File

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'add_order_items_event.dart'; part 'add_order_items_event.dart';
@ -11,26 +12,24 @@ part 'add_order_items_bloc.freezed.dart';
class AddOrderItemsBloc extends Bloc<AddOrderItemsEvent, AddOrderItemsState> { class AddOrderItemsBloc extends Bloc<AddOrderItemsEvent, AddOrderItemsState> {
final OrderRemoteDatasource orderRemoteDatasource; final OrderRemoteDatasource orderRemoteDatasource;
AddOrderItemsBloc( AddOrderItemsBloc(
this.orderRemoteDatasource, this.orderRemoteDatasource,
) : super(const _Initial()) { ) : super(const _Initial()) {
on<_AddOrderItems>((event, emit) async { on<_AddOrderItems>((event, emit) async {
emit(const _Loading()); emit(const _Loading());
try { try {
// Convert ProductQuantity list to the format expected by the API // Convert ProductQuantity list to the format expected by the API
final orderItems = event.items final orderItems = event.items.map((item) => {
.map((item) => { 'id_product': item.product.productId,
'id_product': item.product.id, 'quantity': item.quantity,
'quantity': item.quantity, 'price': item.product.price!.toIntegerFromText,
'price': item.product.price, 'notes': item.notes,
'notes': item.notes, }).toList();
})
.toList();
log("Adding order items: ${orderItems.toString()}"); log("Adding order items: ${orderItems.toString()}");
final result = await orderRemoteDatasource.addOrderItems( final result = await orderRemoteDatasource.addOrderItems(
event.orderId, event.orderId,
orderItems, orderItems,
@ -46,4 +45,4 @@ class AddOrderItemsBloc extends Bloc<AddOrderItemsEvent, AddOrderItemsState> {
} }
}); });
} }
} }

View File

@ -3,6 +3,7 @@ import 'dart:developer';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
@ -32,7 +33,8 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
final subTotal = event.items.fold<int>( final subTotal = event.items.fold<int>(
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + (element.product.price! * element.quantity)); previousValue +
(element.product.price!.toIntegerFromText * element.quantity));
// final total = subTotal + event.tax + event.serviceCharge - event.discount; // final total = subTotal + event.tax + event.serviceCharge - event.discount;
final totalItem = event.items.fold<int>( final totalItem = event.items.fold<int>(
@ -50,7 +52,7 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
total: event.totalPriceFinal, total: event.totalPriceFinal,
paymentMethod: event.paymentMethod, paymentMethod: event.paymentMethod,
totalItem: totalItem, totalItem: totalItem,
idKasir: int.parse(userData.user!.id ?? "0"), idKasir: userData.user?.id ?? 1,
namaKasir: userData.user?.name ?? 'Kasir A', namaKasir: userData.user?.name ?? 'Kasir A',
transactionTime: DateTime.now().toIso8601String(), transactionTime: DateTime.now().toIso8601String(),
customerName: event.customerName, customerName: event.customerName,
@ -64,11 +66,11 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
log("Start 2"); log("Start 2");
//check state online or offline //check state online or offline
log("🔄 About to call API to save order..."); log("🔄 About to call API to save order...");
log("📤 Order data being sent to API: ${dataInput.toServerMap()}"); log("📤 Order data being sent to API: ${dataInput.toServerMap()}");
log("🌐 OrderRemoteDatasource instance: $orderRemoteDatasource"); log("🌐 OrderRemoteDatasource instance: $orderRemoteDatasource");
bool value = false; bool value = false;
try { try {
value = await orderRemoteDatasource.saveOrder(dataInput); value = await orderRemoteDatasource.saveOrder(dataInput);

View File

@ -1,23 +0,0 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'product_loader_event.dart';
part 'product_loader_state.dart';
part 'product_loader_bloc.freezed.dart';
class ProductLoaderBloc extends Bloc<ProductLoaderEvent, ProductLoaderState> {
final ProductRemoteDatasource _productRemoteDatasource;
ProductLoaderBloc(this._productRemoteDatasource)
: super(ProductLoaderState.initial()) {
on<_GetProduct>((event, emit) async {
emit(const _Loading());
final result = await _productRemoteDatasource.getProducts();
result.fold(
(l) => emit(_Error(l)),
(r) => emit(_Loaded(r.data?.products ?? [])),
);
});
}
}

View File

@ -1,790 +0,0 @@
// 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 'product_loader_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 _$ProductLoaderEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() getProduct,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? getProduct,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? getProduct,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_GetProduct value) getProduct,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_GetProduct value)? getProduct,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_GetProduct value)? getProduct,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductLoaderEventCopyWith<$Res> {
factory $ProductLoaderEventCopyWith(
ProductLoaderEvent value, $Res Function(ProductLoaderEvent) then) =
_$ProductLoaderEventCopyWithImpl<$Res, ProductLoaderEvent>;
}
/// @nodoc
class _$ProductLoaderEventCopyWithImpl<$Res, $Val extends ProductLoaderEvent>
implements $ProductLoaderEventCopyWith<$Res> {
_$ProductLoaderEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductLoaderEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$GetProductImplCopyWith<$Res> {
factory _$$GetProductImplCopyWith(
_$GetProductImpl value, $Res Function(_$GetProductImpl) then) =
__$$GetProductImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$GetProductImplCopyWithImpl<$Res>
extends _$ProductLoaderEventCopyWithImpl<$Res, _$GetProductImpl>
implements _$$GetProductImplCopyWith<$Res> {
__$$GetProductImplCopyWithImpl(
_$GetProductImpl _value, $Res Function(_$GetProductImpl) _then)
: super(_value, _then);
/// Create a copy of ProductLoaderEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$GetProductImpl implements _GetProduct {
const _$GetProductImpl();
@override
String toString() {
return 'ProductLoaderEvent.getProduct()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$GetProductImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() getProduct,
}) {
return getProduct();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? getProduct,
}) {
return getProduct?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? getProduct,
required TResult orElse(),
}) {
if (getProduct != null) {
return getProduct();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_GetProduct value) getProduct,
}) {
return getProduct(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_GetProduct value)? getProduct,
}) {
return getProduct?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_GetProduct value)? getProduct,
required TResult orElse(),
}) {
if (getProduct != null) {
return getProduct(this);
}
return orElse();
}
}
abstract class _GetProduct implements ProductLoaderEvent {
const factory _GetProduct() = _$GetProductImpl;
}
/// @nodoc
mixin _$ProductLoaderState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(List<Product> products) loaded,
required TResult Function(String message) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(List<Product> products)? loaded,
TResult? Function(String message)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(List<Product> products)? loaded,
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(_Loaded value) loaded,
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(_Loaded value)? loaded,
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(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductLoaderStateCopyWith<$Res> {
factory $ProductLoaderStateCopyWith(
ProductLoaderState value, $Res Function(ProductLoaderState) then) =
_$ProductLoaderStateCopyWithImpl<$Res, ProductLoaderState>;
}
/// @nodoc
class _$ProductLoaderStateCopyWithImpl<$Res, $Val extends ProductLoaderState>
implements $ProductLoaderStateCopyWith<$Res> {
_$ProductLoaderStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of ProductLoaderState
/// 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 _$ProductLoaderStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of ProductLoaderState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'ProductLoaderState.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(List<Product> products) loaded,
required TResult Function(String message) error,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(List<Product> products)? loaded,
TResult? Function(String message)? error,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(List<Product> products)? loaded,
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(_Loaded value) loaded,
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(_Loaded value)? loaded,
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(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements ProductLoaderState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$ProductLoaderStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of ProductLoaderState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'ProductLoaderState.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(List<Product> products) loaded,
required TResult Function(String message) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(List<Product> products)? loaded,
TResult? Function(String message)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(List<Product> products)? loaded,
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(_Loaded value) loaded,
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(_Loaded value)? loaded,
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(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements ProductLoaderState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$LoadedImplCopyWith<$Res> {
factory _$$LoadedImplCopyWith(
_$LoadedImpl value, $Res Function(_$LoadedImpl) then) =
__$$LoadedImplCopyWithImpl<$Res>;
@useResult
$Res call({List<Product> products});
}
/// @nodoc
class __$$LoadedImplCopyWithImpl<$Res>
extends _$ProductLoaderStateCopyWithImpl<$Res, _$LoadedImpl>
implements _$$LoadedImplCopyWith<$Res> {
__$$LoadedImplCopyWithImpl(
_$LoadedImpl _value, $Res Function(_$LoadedImpl) _then)
: super(_value, _then);
/// Create a copy of ProductLoaderState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? products = null,
}) {
return _then(_$LoadedImpl(
null == products
? _value._products
: products // ignore: cast_nullable_to_non_nullable
as List<Product>,
));
}
}
/// @nodoc
class _$LoadedImpl implements _Loaded {
const _$LoadedImpl(final List<Product> products) : _products = products;
final List<Product> _products;
@override
List<Product> get products {
if (_products is EqualUnmodifiableListView) return _products;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_products);
}
@override
String toString() {
return 'ProductLoaderState.loaded(products: $products)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoadedImpl &&
const DeepCollectionEquality().equals(other._products, _products));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(_products));
/// Create a copy of ProductLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
__$$LoadedImplCopyWithImpl<_$LoadedImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(List<Product> products) loaded,
required TResult Function(String message) error,
}) {
return loaded(products);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(List<Product> products)? loaded,
TResult? Function(String message)? error,
}) {
return loaded?.call(products);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(List<Product> products)? loaded,
TResult Function(String message)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(products);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Loaded value) loaded,
required TResult Function(_Error value) error,
}) {
return loaded(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Loaded value)? loaded,
TResult? Function(_Error value)? error,
}) {
return loaded?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loaded != null) {
return loaded(this);
}
return orElse();
}
}
abstract class _Loaded implements ProductLoaderState {
const factory _Loaded(final List<Product> products) = _$LoadedImpl;
List<Product> get products;
/// Create a copy of ProductLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoadedImplCopyWith<_$LoadedImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @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 _$ProductLoaderStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value, $Res Function(_$ErrorImpl) _then)
: super(_value, _then);
/// Create a copy of ProductLoaderState
/// 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 'ProductLoaderState.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 ProductLoaderState
/// 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(List<Product> products) loaded,
required TResult Function(String message) error,
}) {
return error(message);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(List<Product> products)? loaded,
TResult? Function(String message)? error,
}) {
return error?.call(message);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(List<Product> products)? loaded,
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(_Loaded value) loaded,
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(_Loaded value)? loaded,
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(_Loaded value)? loaded,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements ProductLoaderState {
const factory _Error(final String message) = _$ErrorImpl;
String get message;
/// Create a copy of ProductLoaderState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$ErrorImplCopyWith<_$ErrorImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,6 +0,0 @@
part of 'product_loader_bloc.dart';
@freezed
class ProductLoaderEvent with _$ProductLoaderEvent {
const factory ProductLoaderEvent.getProduct() = _GetProduct;
}

View File

@ -1,9 +0,0 @@
part of 'product_loader_bloc.dart';
@freezed
class ProductLoaderState with _$ProductLoaderState {
const factory ProductLoaderState.initial() = _Initial;
const factory ProductLoaderState.loading() = _Loading;
const factory ProductLoaderState.loaded(List<Product> products) = _Loaded;
const factory ProductLoaderState.error(String message) = _Error;
}

View File

@ -39,7 +39,7 @@ class ProductQuantity {
return { return {
'id_order': orderId, 'id_order': orderId,
'id_product': product.id, 'id_product': product.productId,
'quantity': quantity, 'quantity': quantity,
'price': product.price, 'price': product.price,
'notes': notes, 'notes': notes,
@ -47,11 +47,11 @@ class ProductQuantity {
} }
Map<String, dynamic> toServerMap(int? orderId) { Map<String, dynamic> toServerMap(int? orderId) {
log("toServerMap: ${product.id}"); log("toServerMap: ${product.productId}");
return { return {
'id_order': orderId ?? 0, 'id_order': orderId ?? 0,
'id_product': product.id, 'id_product': product.productId,
'quantity': quantity, 'quantity': quantity,
'price': product.price, 'price': product.price,
'notes': notes, 'notes': notes,

View File

@ -254,7 +254,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
)); ));
return Text( return Text(
@ -314,7 +315,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
)); ));
@ -371,7 +373,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -454,7 +457,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -505,7 +509,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -1257,7 +1262,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -1560,8 +1566,10 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(sum, item) => (sum, item) =>
sum + sum +
(item.product (int.tryParse(item
.price ?? .product
.price ??
'0') ??
0) * 0) *
item.quantity), item.quantity),
); );

View File

@ -282,7 +282,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
)); ));
return Text( return Text(
@ -339,7 +340,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -415,7 +417,8 @@ class _ConfirmPaymentPageState extends State<ConfirmPaymentPage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );

View File

@ -1,13 +1,16 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart'; import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.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/table_model.dart'; import 'package:enaklo_pos/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/bloc/local_product/local_product_bloc.dart';
import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart'; import 'package:enaklo_pos/presentation/home/pages/confirm_payment_page.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/presentation/setting/bloc/sync_product/sync_product_bloc.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import '../../../core/assets/assets.gen.dart'; import '../../../core/assets/assets.gen.dart';
@ -46,15 +49,12 @@ class _HomePageState extends State<HomePage> {
void _syncAndLoadProducts() { void _syncAndLoadProducts() {
// Trigger sync from API first // Trigger sync from API first
// context.read<SyncProductBloc>().add(const SyncProductEvent.syncProduct()); context.read<SyncProductBloc>().add(const SyncProductEvent.syncProduct());
// Also load local products initially in case sync fails or takes time // Also load local products initially in case sync fails or takes time
// context
// .read<LocalProductBloc>()
// .add(const LocalProductEvent.getLocalProduct());
context context
.read<ProductLoaderBloc>() .read<LocalProductBloc>()
.add(const ProductLoaderEvent.getProduct()); .add(const LocalProductEvent.getLocalProduct());
// Initialize checkout with tax and service charge settings // Initialize checkout with tax and service charge settings
context.read<CheckoutBloc>().add(const CheckoutEvent.started()); context.read<CheckoutBloc>().add(const CheckoutEvent.started());
@ -83,7 +83,7 @@ class _HomePageState extends State<HomePage> {
List<Product> products, int categoryId) { List<Product> products, int categoryId) {
final filteredBySearch = _filterProducts(products); final filteredBySearch = _filterProducts(products);
return filteredBySearch return filteredBySearch
.where((element) => element.price == categoryId) .where((element) => element.category?.id == categoryId)
.toList(); .toList();
} }
@ -93,420 +93,451 @@ class _HomePageState extends State<HomePage> {
tag: 'confirmation_screen', tag: 'confirmation_screen',
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.white, backgroundColor: AppColors.white,
body: Row( body: BlocListener<SyncProductBloc, SyncProductState>(
children: [ listener: (context, state) {
Expanded( state.maybeWhen(
flex: 3, orElse: () {},
child: Align( error: (message) {
alignment: AlignmentDirectional.topStart, // If sync fails, still try to load local products
child: Column( context
mainAxisAlignment: MainAxisAlignment.start, .read<LocalProductBloc>()
children: [ .add(const LocalProductEvent.getLocalProduct());
HomeTitle( },
controller: searchController, loaded: (productResponseModel) async {
onChanged: (value) { // Store context reference before async operations
setState(() { final localProductBloc = context.read<LocalProductBloc>();
searchQuery = value;
});
},
),
BlocBuilder<ProductLoaderBloc, ProductLoaderState>(
builder: (context, state) {
return Expanded(
child: CustomTabBarV2(
tabTitles: const [
'Semua',
'Makanan',
'Minuman',
'Paket'
],
tabViews: [
// All Products Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
final filteredProducts =
_filterProducts(products);
if (filteredProducts.isEmpty) {
return const Center(
child: Text('No Items Found'),
);
}
return GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 180,
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 180 / 240,
),
itemBuilder: (context, index) =>
ProductCard(
data: filteredProducts[index],
onCartButton: () {},
),
);
}),
),
// Makanan Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
final filteredProducts =
_filterProductsByCategory(products, 1);
return filteredProducts.isEmpty
? const _IsEmpty()
: GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) =>
ProductCard(
data: filteredProducts[index],
onCartButton: () {},
),
);
}),
),
// Minuman Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
final filteredProducts =
_filterProductsByCategory(products, 2);
return filteredProducts.isEmpty
? const _IsEmpty()
: GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) {
return ProductCard(
data: filteredProducts[index],
onCartButton: () {},
);
},
);
}),
),
// Snack Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
final filteredProducts =
_filterProductsByCategory(products, 3);
return filteredProducts.isEmpty
? const _IsEmpty()
: GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) {
return ProductCard(
data: filteredProducts[index],
onCartButton: () {},
);
},
);
}),
),
],
),
);
},
),
],
),
),
),
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: Material(
color: Colors.white,
child: Column(
children: [
HomeRightTitle(
table: widget.table,
),
Padding(
padding: const EdgeInsets.all(16.0)
.copyWith(bottom: 0, top: 27),
child: Column(
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Item',
style: TextStyle(
color: AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
SizedBox(
width: 130,
),
SizedBox(
width: 50.0,
child: Text(
'Qty',
style: TextStyle(
color: AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
SizedBox(
child: Text(
'Price',
style: TextStyle(
color: AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SpaceHeight(8),
const Divider(),
],
),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: Text('No Items'),
),
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
return ListView.separated(
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) =>
OrderMenu(data: products[index]),
separatorBuilder: (context, index) =>
const SpaceHeight(1.0),
itemCount: products.length,
);
},
);
},
),
const SpaceHeight(8.0),
],
),
),
),
Padding(
padding: const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column(
children: [
const Divider(),
const SpaceHeight(16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Pajak',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final tax = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return tax;
});
return Text(
'$tax %',
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w600,
),
);
},
),
],
),
const SpaceHeight(16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Sub total',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final price = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return products
.map((e) =>
e.product.price! * e.quantity)
.reduce((value, element) =>
value + element);
});
return Text( // Save synced products to local database
price.currencyFormatRp, await ProductLocalDatasource.instance.deleteAllProducts();
style: const TextStyle( await ProductLocalDatasource.instance.insertProducts(
color: AppColors.primary, productResponseModel.data!,
fontWeight: FontWeight.w900, );
// Then load local products to display
localProductBloc.add(const LocalProductEvent.getLocalProduct());
},
);
},
child: Row(
children: [
Expanded(
flex: 3,
child: Align(
alignment: AlignmentDirectional.topStart,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
HomeTitle(
controller: searchController,
onChanged: (value) {
setState(() {
searchQuery = value;
});
},
),
BlocBuilder<LocalProductBloc, LocalProductState>(
builder: (context, state) {
return Expanded(
child: CustomTabBarV2(
tabTitles: const [
'Semua',
'Makanan',
'Minuman',
'Paket'
],
tabViews: [
// All Products Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
final filteredProducts =
_filterProducts(products);
if (filteredProducts.isEmpty) {
return const Center(
child: Text('No Items Found'),
);
}
return GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 180,
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 180 / 240,
),
itemBuilder: (context, index) =>
ProductCard(
data: filteredProducts[index],
onCartButton: () {},
), ),
); );
}, }),
),
// Makanan Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
final filteredProducts =
_filterProductsByCategory(products, 1);
return filteredProducts.isEmpty
? const _IsEmpty()
: GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) =>
ProductCard(
data: filteredProducts[index],
onCartButton: () {},
),
);
}),
),
// Minuman Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
final filteredProducts =
_filterProductsByCategory(products, 2);
return filteredProducts.isEmpty
? const _IsEmpty()
: GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) {
return ProductCard(
data: filteredProducts[index],
onCartButton: () {},
);
},
);
}),
),
// Snack Tab
SizedBox(
child: state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loading: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (products) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
final filteredProducts =
_filterProductsByCategory(products, 3);
return filteredProducts.isEmpty
? const _IsEmpty()
: GridView.builder(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) {
return ProductCard(
data: filteredProducts[index],
onCartButton: () {},
);
},
);
}),
), ),
], ],
), ),
SpaceHeight(16.0), );
Align( },
alignment: Alignment.bottomCenter,
child: Expanded(
child: Button.filled(
borderRadius: 12,
elevation: 1,
onPressed: () {
context.push(ConfirmPaymentPage(
isTable: widget.isTable,
table: widget.table,
));
},
label: 'Lanjutkan Pembayaran',
),
),
),
],
),
), ),
], ],
), ),
), ),
), ),
), Expanded(
], flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: Material(
color: Colors.white,
child: Column(
children: [
HomeRightTitle(
table: widget.table,
),
Padding(
padding: const EdgeInsets.all(16.0)
.copyWith(bottom: 0, top: 27),
child: Column(
children: [
const Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Item',
style: TextStyle(
color: AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
SizedBox(
width: 130,
),
SizedBox(
width: 50.0,
child: Text(
'Qty',
style: TextStyle(
color: AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
SizedBox(
child: Text(
'Price',
style: TextStyle(
color: AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SpaceHeight(8),
const Divider(),
],
),
),
Expanded(
child: SingleChildScrollView(
padding:
const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: Text('No Items'),
),
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
return ListView.separated(
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) =>
OrderMenu(data: products[index]),
separatorBuilder: (context, index) =>
const SpaceHeight(1.0),
itemCount: products.length,
);
},
);
},
),
const SpaceHeight(8.0),
],
),
),
),
Padding(
padding: const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column(
children: [
const Divider(),
const SpaceHeight(16.0),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(
'Pajak',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final tax = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return tax;
});
return Text(
'$tax %',
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w600,
),
);
},
),
],
),
const SpaceHeight(16.0),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(
'Sub total',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final price = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return products
.map((e) =>
e.product.price!
.toIntegerFromText *
e.quantity)
.reduce((value, element) =>
value + element);
});
return Text(
price.currencyFormatRp,
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w900,
),
);
},
),
],
),
SpaceHeight(16.0),
Align(
alignment: Alignment.bottomCenter,
child: Expanded(
child: Button.filled(
borderRadius: 12,
elevation: 1,
onPressed: () {
context.push(ConfirmPaymentPage(
isTable: widget.isTable,
table: widget.table,
));
},
label: 'Lanjutkan Pembayaran',
),
),
),
],
),
),
],
),
),
),
),
],
),
), ),
), ),
); );

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/constants/variables.dart'; import 'package:enaklo_pos/core/constants/variables.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart'; import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
@ -51,7 +52,7 @@ class _OrderMenuState extends State<OrderMenu> {
child: ListTile( child: ListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
leading: ClipRRect( leading: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8.0)), borderRadius: BorderRadius.all(Radius.circular(50.0)),
child: child:
// Icon( // Icon(
// Icons.fastfood, // Icons.fastfood,
@ -59,23 +60,14 @@ class _OrderMenuState extends State<OrderMenu> {
// color: AppColors.primary, // color: AppColors.primary,
// ), // ),
CachedNetworkImage( CachedNetworkImage(
imageUrl: widget.data.product.name!.contains('http') imageUrl: widget.data.product.image!.contains('http')
? widget.data.product.name! ? widget.data.product.image!
: '${Variables.baseUrl}/${widget.data.product.name}', : '${Variables.baseUrl}/${widget.data.product.image}',
width: 50.0, width: 50.0,
height: 50.0, height: 50.0,
fit: BoxFit.cover, fit: BoxFit.cover,
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) =>
width: 50.0, const Icon(Icons.error),
height: 50.0,
decoration: BoxDecoration(
color: AppColors.disabled.withOpacity(0.4),
),
child: const Icon(
Icons.image,
color: AppColors.grey,
),
),
), ),
), ),
title: Row( title: Row(
@ -94,7 +86,8 @@ class _OrderMenuState extends State<OrderMenu> {
subtitle: Column( subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(widget.data.product.price!.currencyFormatRp), Text(widget.data.product.price!.toIntegerFromText
.currencyFormatRp),
], ],
), ),
), ),
@ -146,7 +139,8 @@ class _OrderMenuState extends State<OrderMenu> {
SizedBox( SizedBox(
width: 80.0, width: 80.0,
child: Text( child: Text(
(widget.data.product.price! * widget.data.quantity) (widget.data.product.price!.toIntegerFromText *
widget.data.quantity)
.currencyFormatRp, .currencyFormatRp,
textAlign: TextAlign.right, textAlign: TextAlign.right,
style: const TextStyle( style: const TextStyle(

View File

@ -1,8 +1,9 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/constants/variables.dart'; import 'package:enaklo_pos/core/constants/variables.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/product_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
@ -42,9 +43,9 @@ class ProductCard extends StatelessWidget {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(8.0)), borderRadius: BorderRadius.all(Radius.circular(8.0)),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: data.name!.contains('http') imageUrl: data.image!.contains('http')
? data.name! ? data.image!
: '${Variables.baseUrl}/${data.name}', : '${Variables.baseUrl}/${data.image}',
fit: BoxFit.cover, fit: BoxFit.cover,
width: double.infinity, width: double.infinity,
height: 120, height: 120,
@ -75,7 +76,7 @@ class ProductCard extends StatelessWidget {
Align( Align(
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'-', data.category?.name ?? '-',
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
color: AppColors.grey, color: AppColors.grey,
@ -89,7 +90,7 @@ class ProductCard extends StatelessWidget {
Align( Align(
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
data.price!.currencyFormatRp, data.price!.toIntegerFromText.currencyFormatRp,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 12, fontSize: 12,

View File

@ -62,7 +62,7 @@ class SalesListOrder extends StatelessWidget {
), ),
), ),
Text( Text(
(product.product.price ?? 0) as String, product.product.price ?? '',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
), ),
@ -76,7 +76,7 @@ class SalesListOrder extends StatelessWidget {
), ),
), ),
Text( Text(
(product.product.price ?? 0) as String, product.product.price ?? '',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
), ),

View File

@ -20,10 +20,10 @@ class AddProductBloc extends Bloc<AddProductEvent, AddProductState> {
emit(const _Loading()); emit(const _Loading());
final requestData = ProductRequestModel( final requestData = ProductRequestModel(
name: event.product.name!, name: event.product.name!,
price: event.product.price!, price: int.parse(event.product.price!),
stock: 0, stock: event.product.stock!,
categoryId: 0, categoryId: event.product.categoryId!,
isBestSeller: 0, isBestSeller: event.product.isFavorite!,
image: event.image, image: event.image,
); );
log("requestData: ${requestData.toString()}"); log("requestData: ${requestData.toString()}");

View File

@ -1,3 +1,4 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart';
@ -18,7 +19,7 @@ class GetProductsBloc extends Bloc<GetProductsEvent, GetProductsState> {
response.fold( response.fold(
(l) => emit(_Error(l)), (l) => emit(_Error(l)),
(r) { (r) {
emit(_Success(r.data!.products!)); emit(_Success(r.data!));
}, },
); );
}); });

View File

@ -19,58 +19,57 @@ class UpdateProductBloc extends Bloc<UpdateProductEvent, UpdateProductState> {
) : super(const _Initial()) { ) : super(const _Initial()) {
on<_UpdateProduct>((event, emit) async { on<_UpdateProduct>((event, emit) async {
emit(const _Loading()); emit(const _Loading());
try { try {
// Validate required fields // Validate required fields
if (event.product.name == null || event.product.name!.isEmpty) { if (event.product.name == null || event.product.name!.isEmpty) {
emit(_Error('Product name is required')); emit(_Error('Product name is required'));
return; return;
} }
if (event.product.price == null || event.product.price == 0) { if (event.product.price == null || event.product.price!.isEmpty) {
emit(_Error('Product price is required')); emit(_Error('Product price is required'));
return; return;
} }
// if (event.product.stock == null) { if (event.product.stock == null) {
// emit(_Error('Product stock is required')); emit(_Error('Product stock is required'));
// return; return;
// } }
if (event.product.categoryId == null) { if (event.product.categoryId == null) {
emit(_Error('Product category is required')); emit(_Error('Product category is required'));
return; return;
} }
// Parse price safely // Parse price safely
final price = event.product.price!; final price = int.tryParse(event.product.price!);
if (price == 0) { if (price == null) {
emit(_Error('Invalid price format')); emit(_Error('Invalid price format'));
return; return;
} }
final requestData = ProductRequestModel( final requestData = ProductRequestModel(
id: event.product.id, id: event.product.id,
name: event.product.name!, name: event.product.name!,
price: price, price: price,
stock: 0, stock: event.product.stock!,
categoryId: 0, categoryId: event.product.categoryId!,
isBestSeller: 0, // Default to 0 if null isBestSeller: event.product.isFavorite ?? 0, // Default to 0 if null
image: event.image, image: event.image,
printerType: 'kitchen', // Default to kitchen if null printerType: event.product.printerType ?? 'kitchen', // Default to kitchen if null
); );
log("Update requestData: ${requestData.toString()}"); log("Update requestData: ${requestData.toString()}");
log("Request map: ${requestData.toMap()}"); log("Request map: ${requestData.toMap()}");
final response = await datasource.updateProduct(requestData); final response = await datasource.updateProduct(requestData);
response.fold( response.fold(
(l) => emit(_Error(l)), (l) => emit(_Error(l)),
(r) async { (r) async {
// Update local database after successful API update // Update local database after successful API update
try { try {
await ProductLocalDatasource.instance await ProductLocalDatasource.instance.updateProduct(event.product);
.updateProduct(event.product);
log("Local product updated successfully"); log("Local product updated successfully");
} catch (e) { } catch (e) {
log("Error updating local product: $e"); log("Error updating local product: $e");
@ -84,4 +83,4 @@ class UpdateProductBloc extends Bloc<UpdateProductEvent, UpdateProductState> {
} }
}); });
} }
} }

View File

@ -28,9 +28,9 @@ class DetailProductDialog extends StatelessWidget {
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: product.name!.contains('http') imageUrl: product.image!.contains('http')
? product.name! ? product.image!
: '${Variables.baseUrl}/${product.name}', : '${Variables.baseUrl}/${product.image}',
fit: BoxFit.cover, fit: BoxFit.cover,
width: 120, width: 120,
height: 120, height: 120,
@ -88,20 +88,20 @@ class DetailProductDialog extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildItem( _buildItem(
"-", product.category?.name ?? "-",
"Kategori", "Kategori",
), ),
// _buildItem(
// "${product.stock}",
// "Stok",
// valueColor: product.stock! < 50
// ? AppColors.red
// : product.stock! < 100
// ? Colors.yellow
// : AppColors.green,
// ),
_buildItem( _buildItem(
(product.price ?? 0).toString().currencyFormatRpV2, "${product.stock}",
"Stok",
valueColor: product.stock! < 50
? AppColors.red
: product.stock! < 100
? Colors.yellow
: AppColors.green,
),
_buildItem(
(product.price ?? "0").currencyFormatRpV2,
"Harga", "Harga",
valueColor: AppColors.primary, valueColor: AppColors.primary,
), ),
@ -142,11 +142,11 @@ class DetailProductDialog extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: product.isActive == true ? AppColors.green : AppColors.red, color: product.status == 1 ? AppColors.green : AppColors.red,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
product.isActive == true ? 'Aktif' : 'Tidak Aktif', product.status == 1 ? 'Aktif' : 'Tidak Aktif',
style: const TextStyle( style: const TextStyle(
color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700), color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700),
), ),

View File

@ -57,12 +57,12 @@ class _FormProductDialogState extends State<FormProductDialog> {
// Pre-fill the form with existing product data // Pre-fill the form with existing product data
final product = widget.product!; final product = widget.product!;
nameController!.text = product.name ?? ''; nameController!.text = product.name ?? '';
priceValue = product.price ?? 0; priceValue = int.tryParse(product.price ?? '0') ?? 0;
priceController!.text = priceValue.currencyFormatRp; priceController!.text = priceValue.currencyFormatRp;
stockController!.text = ''; stockController!.text = (product.stock ?? 0).toString();
isBestSeller = false; isBestSeller = product.isFavorite == 1;
printType = 'kitchen'; printType = product.printerType ?? 'kitchen';
imageUrl = ''; imageUrl = product.image;
} }
super.initState(); super.initState();
@ -129,72 +129,72 @@ class _FormProductDialogState extends State<FormProductDialog> {
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
), ),
const SpaceHeight(20.0), const SpaceHeight(20.0),
// const Text( const Text(
// "Kategori", "Kategori",
// style: TextStyle( style: TextStyle(
// fontSize: 14, fontSize: 14,
// fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
// ), ),
// ), ),
// const SpaceHeight(12.0), const SpaceHeight(12.0),
// BlocBuilder<GetCategoriesBloc, GetCategoriesState>( BlocBuilder<GetCategoriesBloc, GetCategoriesState>(
// builder: (context, state) { builder: (context, state) {
// return state.maybeWhen( return state.maybeWhen(
// orElse: () { orElse: () {
// return const Center( return const Center(
// child: CircularProgressIndicator(), child: CircularProgressIndicator(),
// ); );
// }, },
// success: (categories) { success: (categories) {
// // Set the selected category if in edit mode and not already set // Set the selected category if in edit mode and not already set
// if (isEditMode && if (isEditMode &&
// selectCategory == null && selectCategory == null &&
// widget.product?.category != null) { widget.product?.category != null) {
// try { try {
// selectCategory = categories.firstWhere( selectCategory = categories.firstWhere(
// (cat) => cat.id == widget.product!.category!.id, (cat) => cat.id == widget.product!.category!.id,
// ); );
// } catch (e) { } catch (e) {
// // If no exact match found, leave selectCategory as null // If no exact match found, leave selectCategory as null
// // This will show the hint text instead // This will show the hint text instead
// log("No matching category found for product category ID: ${widget.product!.category!.id}"); log("No matching category found for product category ID: ${widget.product!.category!.id}");
// } }
// } }
// return DropdownButtonHideUnderline( return DropdownButtonHideUnderline(
// child: Container( child: Container(
// decoration: BoxDecoration( decoration: BoxDecoration(
// border: Border.all(color: Colors.grey), border: Border.all(color: Colors.grey),
// borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
// ), ),
// padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
// horizontal: 10, vertical: 5), horizontal: 10, vertical: 5),
// child: DropdownButton<CategoryModel>( child: DropdownButton<CategoryModel>(
// value: selectCategory, value: selectCategory,
// hint: const Text("Pilih Kategori"), hint: const Text("Pilih Kategori"),
// isExpanded: true, // Untuk mengisi lebar container isExpanded: true, // Untuk mengisi lebar container
// onChanged: (newValue) { onChanged: (newValue) {
// if (newValue != null) { if (newValue != null) {
// selectCategory = newValue; selectCategory = newValue;
// setState(() {}); setState(() {});
// log("selectCategory: ${selectCategory!.name}"); log("selectCategory: ${selectCategory!.name}");
// } }
// }, },
// items: categories items: categories
// .map<DropdownMenuItem<CategoryModel>>( .map<DropdownMenuItem<CategoryModel>>(
// (CategoryModel category) { (CategoryModel category) {
// return DropdownMenuItem<CategoryModel>( return DropdownMenuItem<CategoryModel>(
// value: category, value: category,
// child: Text(category.name!), child: Text(category.name!),
// ); );
// }).toList(), }).toList(),
// ), ),
// ), ),
// ); );
// }, },
// ); );
// }, },
// ), ),
const SpaceHeight(12.0), const SpaceHeight(12.0),
const Text( const Text(
"Tipe Print", "Tipe Print",
@ -296,23 +296,23 @@ class _FormProductDialogState extends State<FormProductDialog> {
return; return;
} }
// log("isBestSeller: $isBestSeller"); log("isBestSeller: $isBestSeller");
// final String name = nameController!.text; final String name = nameController!.text;
// final int stock = final int stock =
// stockController!.text.toIntegerFromText; stockController!.text.toIntegerFromText;
// final Product product = widget.product!.copyWith( final Product product = widget.product!.copyWith(
// name: name, name: name,
// price: priceValue.toString(), price: priceValue.toString(),
// stock: stock, stock: stock,
// categoryId: selectCategory!.id!, categoryId: selectCategory!.id!,
// isFavorite: isBestSeller ? 1 : 0, isFavorite: isBestSeller ? 1 : 0,
// printerType: printType, printerType: printType,
// ); );
// context.read<UpdateProductBloc>().add( context.read<UpdateProductBloc>().add(
// UpdateProductEvent.updateProduct( UpdateProductEvent.updateProduct(
// product, imageFile)); product, imageFile));
}, },
label: 'Ubah Produk', label: 'Ubah Produk',
); );
@ -354,33 +354,33 @@ class _FormProductDialogState extends State<FormProductDialog> {
orElse: () { orElse: () {
return Button.filled( return Button.filled(
onPressed: () { onPressed: () {
// if (selectCategory == null) { if (selectCategory == null) {
// ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
// const SnackBar( const SnackBar(
// content: Text('Please select a category'), content: Text('Please select a category'),
// backgroundColor: Colors.red, backgroundColor: Colors.red,
// ), ),
// ); );
// return; return;
// } }
// log("isBestSeller: $isBestSeller"); log("isBestSeller: $isBestSeller");
// final String name = nameController!.text; final String name = nameController!.text;
// final int stock = final int stock =
// stockController!.text.toIntegerFromText; stockController!.text.toIntegerFromText;
// final Product product = Product( final Product product = Product(
// name: name, name: name,
// price: priceValue.toString(), price: priceValue.toString(),
// stock: stock, stock: stock,
// categoryId: selectCategory!.id!, categoryId: selectCategory!.id!,
// isFavorite: isBestSeller ? 1 : 0, isFavorite: isBestSeller ? 1 : 0,
// image: imageFile!.path, image: imageFile!.path,
// printerType: printType, printerType: printType,
// ); );
// context.read<AddProductBloc>().add( context.read<AddProductBloc>().add(
// AddProductEvent.addProduct( AddProductEvent.addProduct(
// product, imageFile!)); product, imageFile!));
}, },
label: 'Simpan Produk', label: 'Simpan Produk',
); );
@ -441,14 +441,14 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
if (isEditMode) { if (isEditMode) {
// Pre-fill the form with existing product data // Pre-fill the form with existing product data
// final product = widget.product!; final product = widget.product!;
// nameController!.text = product.name ?? ''; nameController!.text = product.name ?? '';
// priceValue = int.tryParse(product.price ?? '0') ?? 0; priceValue = int.tryParse(product.price ?? '0') ?? 0;
// priceController!.text = priceValue.currencyFormatRp; priceController!.text = priceValue.currencyFormatRp;
// stockController!.text = (product.stock ?? 0).toString(); stockController!.text = (product.stock ?? 0).toString();
// isBestSeller = product.isFavorite == 1; isBestSeller = product.isFavorite == 1;
// printType = product.printerType ?? 'kitchen'; printType = product.printerType ?? 'kitchen';
// imageUrl = product.image; imageUrl = product.image;
} }
super.initState(); super.initState();
@ -542,19 +542,19 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
}, },
success: (categories) { success: (categories) {
// Set the selected category if in edit mode and not already set // Set the selected category if in edit mode and not already set
// if (isEditMode && if (isEditMode &&
// selectCategory == null && selectCategory == null &&
// widget.product?.category != null) { widget.product?.category != null) {
// try { try {
// selectCategory = categories.firstWhere( selectCategory = categories.firstWhere(
// (cat) => cat.id == widget.product!.category!.id, (cat) => cat.id == widget.product!.category!.id,
// ); );
// } catch (e) { } catch (e) {
// // If no exact match found, leave selectCategory as null // If no exact match found, leave selectCategory as null
// // This will show the hint text instead // This will show the hint text instead
// log("No matching category found for product category ID: ${widget.product!.category!.id}"); log("No matching category found for product category ID: ${widget.product!.category!.id}");
// } }
// } }
return DropdownButtonHideUnderline( return DropdownButtonHideUnderline(
child: Container( child: Container(
@ -696,18 +696,18 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
final int stock = final int stock =
stockController!.text.toIntegerFromText; stockController!.text.toIntegerFromText;
// final Product product = widget.product!.copyWith( final Product product = widget.product!.copyWith(
// name: name, name: name,
// price: priceValue.toString(), price: priceValue.toString(),
// stock: stock, stock: stock,
// categoryId: selectCategory!.id!, categoryId: selectCategory!.id!,
// isFavorite: isBestSeller ? 1 : 0, isFavorite: isBestSeller ? 1 : 0,
// printerType: printType, printerType: printType,
// ); );
// context.read<UpdateProductBloc>().add( context.read<UpdateProductBloc>().add(
// UpdateProductEvent.updateProduct( UpdateProductEvent.updateProduct(
// product, imageFile)); product, imageFile));
}, },
label: 'Update Product', label: 'Update Product',
); );
@ -764,18 +764,18 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
final int stock = final int stock =
stockController!.text.toIntegerFromText; stockController!.text.toIntegerFromText;
// final Product product = Product( final Product product = Product(
// name: name, name: name,
// price: priceValue.toString(), price: priceValue.toString(),
// stock: stock, stock: stock,
// categoryId: selectCategory!.id!, categoryId: selectCategory!.id!,
// isFavorite: isBestSeller ? 1 : 0, isFavorite: isBestSeller ? 1 : 0,
// image: imageFile!.path, image: imageFile!.path,
// printerType: printType, printerType: printType,
// ); );
// context.read<AddProductBloc>().add( context.read<AddProductBloc>().add(
// AddProductEvent.addProduct( AddProductEvent.addProduct(
// product, imageFile!)); product, imageFile!));
}, },
label: 'Save Product', label: 'Save Product',
); );

View File

@ -62,7 +62,7 @@ class _SyncDataPageState extends State<SyncDataPage> {
.deleteAllProducts(); .deleteAllProducts();
await ProductLocalDatasource.instance await ProductLocalDatasource.instance
.insertProducts( .insertProducts(
productResponseModel.data!.products!, productResponseModel.data!,
); );
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(

View File

@ -36,9 +36,9 @@ class MenuProductItem extends StatelessWidget {
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: data.name!.contains('http') imageUrl: data.image!.contains('http')
? data.name! ? data.image!
: '${Variables.baseUrl}/${data.name}', : '${Variables.baseUrl}/${data.image}',
fit: BoxFit.cover, fit: BoxFit.cover,
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: double.infinity, width: double.infinity,
@ -67,7 +67,7 @@ class MenuProductItem extends StatelessWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
"", data.category?.name ?? "",
style: const TextStyle( style: const TextStyle(
color: AppColors.white, color: AppColors.white,
fontSize: 10, fontSize: 10,
@ -185,7 +185,7 @@ class MenuProductItemOld extends StatelessWidget {
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(10.0)), borderRadius: const BorderRadius.all(Radius.circular(10.0)),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: '${Variables.baseUrl}/${data.name}', imageUrl: '${Variables.baseUrl}/${data.image}',
placeholder: (context, url) => placeholder: (context, url) =>
const Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => const Icon( errorWidget: (context, url, error) => const Icon(
@ -214,7 +214,7 @@ class MenuProductItemOld extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
Text( Text(
'-', data.category?.name ?? '-',
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey, color: Colors.grey,
@ -260,7 +260,7 @@ class MenuProductItemOld extends StatelessWidget {
Radius.circular(10.0)), Radius.circular(10.0)),
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: imageUrl:
'${Variables.baseUrl}${data.name}', '${Variables.baseUrl}${data.image}',
placeholder: (context, url) => placeholder: (context, url) =>
const Center( const Center(
child: child:
@ -275,7 +275,7 @@ class MenuProductItemOld extends StatelessWidget {
), ),
const SpaceHeight(10.0), const SpaceHeight(10.0),
Text( Text(
'-', data.category?.name ?? '-',
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey, color: Colors.grey,
@ -291,7 +291,7 @@ class MenuProductItemOld extends StatelessWidget {
), ),
const SpaceHeight(10.0), const SpaceHeight(10.0),
Text( Text(
"data.stock.toString()", data.stock.toString(),
style: const TextStyle( style: const TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.grey, color: Colors.grey,

View File

@ -26,7 +26,7 @@ class DraftOrderItem {
Map<String, dynamic> toMapForLocal(int orderId) { Map<String, dynamic> toMapForLocal(int orderId) {
return { return {
'id_draft_order': orderId, 'id_draft_order': orderId,
'id_product': product.id, 'id_product': product.productId,
'quantity': quantity, 'quantity': quantity,
'price': product.price, 'price': product.price,
}; };

View File

@ -46,7 +46,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
PaymentMethod? selectedPaymentMethod; PaymentMethod? selectedPaymentMethod;
int totalPriceFinal = 0; int totalPriceFinal = 0;
int discountAmountFinal = 0; int discountAmountFinal = 0;
// Helper method to handle post-payment cleanup // Helper method to handle post-payment cleanup
Future<void> _handlePostPaymentCleanup() async { Future<void> _handlePostPaymentCleanup() async {
if (widget.table != null && widget.draftOrder?.id != null) { if (widget.table != null && widget.draftOrder?.id != null) {
@ -60,22 +60,21 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
startTime: DateTime.now().toIso8601String(), startTime: DateTime.now().toIso8601String(),
position: widget.table!.position, position: widget.table!.position,
); );
// Update table status // Update table status
await ProductLocalDatasource.instance.updateStatusTable(newTable); await ProductLocalDatasource.instance.updateStatusTable(newTable);
// Remove draft order // Remove draft order
await ProductLocalDatasource.instance await ProductLocalDatasource.instance.removeDraftOrderById(widget.draftOrder!.id!);
.removeDraftOrderById(widget.draftOrder!.id!);
// Refresh table status // Refresh table status
context.read<GetTableStatusBloc>().add( context.read<GetTableStatusBloc>().add(
GetTableStatusEvent.getTablesStatus('all'), GetTableStatusEvent.getTablesStatus('all'),
); );
log("Table ${widget.table!.tableName} freed up and draft order removed"); log("Table ${widget.table!.tableName} freed up and draft order removed");
} }
// Safely navigate back - pop multiple times to get to table management // Safely navigate back - pop multiple times to get to table management
// Pop the success dialog first, then the payment page // Pop the success dialog first, then the payment page
if (Navigator.of(context).canPop()) { if (Navigator.of(context).canPop()) {
@ -85,7 +84,6 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
} }
} }
} }
@override @override
void initState() { void initState() {
context context
@ -95,7 +93,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
.read<PaymentMethodsBloc>() .read<PaymentMethodsBloc>()
.add(PaymentMethodsEvent.fetchPaymentMethods()); .add(PaymentMethodsEvent.fetchPaymentMethods());
super.initState(); super.initState();
// Set a default payment method in case API fails // Set a default payment method in case API fails
selectedPaymentMethod = PaymentMethod( selectedPaymentMethod = PaymentMethod(
id: 1, id: 1,
@ -137,8 +135,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('Kembali'), title: const Text('Kembali'),
content: const Text( content: const Text('Apakah Anda yakin ingin kembali? Order akan tetap tersimpan.'),
'Apakah Anda yakin ingin kembali? Order akan tetap tersimpan.'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
@ -149,8 +146,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); // Close dialog Navigator.of(context).pop(); // Close dialog
Navigator.of(context) Navigator.of(context).pop(); // Go back to previous page
.pop(); // Go back to previous page
}, },
child: const Text('Ya'), child: const Text('Ya'),
), ),
@ -161,8 +157,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
}, },
child: const Text( child: const Text(
'Kembali', 'Kembali',
style: TextStyle( style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
color: Colors.white, fontWeight: FontWeight.bold),
), ),
), ),
], ],
@ -256,8 +251,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) {
orderType) {
if (products.isEmpty) { if (products.isEmpty) {
return const Center( return const Center(
child: Text('No Items'), child: Text('No Items'),
@ -300,13 +294,13 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
products.fold( products.fold(
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
)); ));
return Text( return Text(
@ -380,8 +374,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
tax, tax,
); );
final price = state.maybeWhen( final price = state.maybeWhen(
@ -394,13 +387,13 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
products.fold( products.fold(
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -415,8 +408,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) {
orderType) {
return discountAmount; return discountAmount;
}); });
@ -454,8 +446,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
tax, tax,
); );
final price = state.maybeWhen( final price = state.maybeWhen(
@ -468,13 +459,13 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
products.fold( products.fold(
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -489,8 +480,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) {
orderType) {
return discountAmount; return discountAmount;
}); });
@ -504,8 +494,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
serviceCharge, serviceCharge,
); );
@ -547,13 +536,13 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
products.fold( products.fold(
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -568,8 +557,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) {
orderType) {
return discountAmount; return discountAmount;
}); });
@ -583,8 +571,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
serviceCharge, serviceCharge,
); );
@ -598,8 +585,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) =>
orderType) =>
tax, tax,
); );
@ -680,8 +666,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
serviceCharge, serviceCharge,
totalQuantity, totalQuantity,
totalPrice, totalPrice,
draftName, draftName, orderType) {
orderType) {
customerController.text = draftName; customerController.text = draftName;
return TextFormField( return TextFormField(
readOnly: true, readOnly: true,
@ -714,8 +699,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
), ),
), ),
const SpaceHeight(12.0), const SpaceHeight(12.0),
BlocBuilder<PaymentMethodsBloc, BlocBuilder<PaymentMethodsBloc, PaymentMethodsState>(
PaymentMethodsState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
@ -733,16 +717,14 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
error: (message) => Column( error: (message) => Column(
children: [ children: [
Center( Center(
child: Text( child: Text('Error loading payment methods: $message'),
'Error loading payment methods: $message'),
), ),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Button.filled( Button.filled(
onPressed: () { onPressed: () {
context context
.read<PaymentMethodsBloc>() .read<PaymentMethodsBloc>()
.add(PaymentMethodsEvent .add(PaymentMethodsEvent.fetchPaymentMethods());
.fetchPaymentMethods());
}, },
label: 'Retry', label: 'Retry',
), ),
@ -757,39 +739,32 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
return Column( return Column(
children: [ children: [
const Center( const Center(
child: Text( child: Text('No payment methods available'),
'No payment methods available'),
), ),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Button.filled( Button.filled(
onPressed: () { onPressed: () {
context context
.read<PaymentMethodsBloc>() .read<PaymentMethodsBloc>()
.add(PaymentMethodsEvent .add(PaymentMethodsEvent.fetchPaymentMethods());
.fetchPaymentMethods());
}, },
label: 'Retry', label: 'Retry',
), ),
], ],
); );
} }
// Set default selected payment method if none selected or if current selection is not in the list // Set default selected payment method if none selected or if current selection is not in the list
if (selectedPaymentMethod == null || if (selectedPaymentMethod == null ||
!paymentMethods.any((method) => !paymentMethods.any((method) => method.id == selectedPaymentMethod?.id)) {
method.id == selectedPaymentMethod = paymentMethods.first;
selectedPaymentMethod?.id)) {
selectedPaymentMethod =
paymentMethods.first;
} }
return Wrap( return Wrap(
spacing: 12.0, spacing: 12.0,
runSpacing: 8.0, runSpacing: 8.0,
children: paymentMethods.map((method) { children: paymentMethods.map((method) {
final isSelected = final isSelected = selectedPaymentMethod?.id == method.id;
selectedPaymentMethod?.id ==
method.id;
return Container( return Container(
constraints: const BoxConstraints( constraints: const BoxConstraints(
minWidth: 120.0, minWidth: 120.0,
@ -800,44 +775,31 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
color: AppColors.primary, color: AppColors.primary,
width: 2.0, width: 2.0,
), ),
borderRadius: borderRadius: BorderRadius.circular(8.0),
BorderRadius.circular(
8.0),
) )
: null, : null,
child: Tooltip( child: Tooltip(
message: method.description ?? message: method.description ?? 'No description available',
'No description available',
child: isSelected child: isSelected
? Button.filled( ? Button.filled(
width: double.infinity, width: double.infinity,
height: 50.0, height: 50.0,
onPressed: () { onPressed: () {
setState(() { setState(() {
selectedPaymentMethod = selectedPaymentMethod = method;
method;
}); });
}, },
label: method.name label: method.name?.isNotEmpty == true ? method.name! : 'Unknown',
?.isNotEmpty ==
true
? method.name!
: 'Unknown',
) )
: Button.outlined( : Button.outlined(
width: double.infinity, width: double.infinity,
height: 50.0, height: 50.0,
onPressed: () { onPressed: () {
setState(() { setState(() {
selectedPaymentMethod = selectedPaymentMethod = method;
method;
}); });
}, },
label: method.name label: method.name?.isNotEmpty == true ? method.name! : 'Unknown',
?.isNotEmpty ==
true
? method.name!
: 'Unknown',
), ),
), ),
); );
@ -919,41 +881,30 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: Row( title: Row(
children: [ children: [
Icon(Icons.warning, Icon(Icons.warning, color: AppColors.red),
color: AppColors.red),
SizedBox(width: 8), SizedBox(width: 8),
Text('Batalkan Pesanan?'), Text('Batalkan Pesanan?'),
], ],
), ),
content: Text( content: Text(
'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table?.tableName ?? "ini"}?\n\nPesanan akan dihapus secara permanen.'), 'Apakah anda yakin ingin membatalkan pesanan untuk meja ${widget.table?.tableName ?? "ini"}?\n\nPesanan akan dihapus secara permanen.'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => onPressed: () => Navigator.pop(context),
Navigator.pop(context), child: Text('Tidak',
child: Text('Tidak', style: TextStyle(color: AppColors.primary)),
style: TextStyle(
color:
AppColors.primary)),
), ),
BlocListener<StatusTableBloc, BlocListener<StatusTableBloc, StatusTableState>(
StatusTableState>(
listener: (context, state) { listener: (context, state) {
state.maybeWhen( state.maybeWhen(
orElse: () {}, orElse: () {},
success: () { success: () {
Navigator.pop( Navigator.pop(context); // Close void dialog
context); // Close void dialog Navigator.pop(context); // Close payment page
Navigator.pop( ScaffoldMessenger.of(context).showSnackBar(
context); // Close payment page
ScaffoldMessenger.of(
context)
.showSnackBar(
const SnackBar( const SnackBar(
content: Text( content: Text('Pesanan berhasil dibatalkan'),
'Pesanan berhasil dibatalkan'), backgroundColor: AppColors.primary,
backgroundColor:
AppColors.primary,
), ),
); );
}, },
@ -961,47 +912,34 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
}, },
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: backgroundColor: AppColors.red,
AppColors.red,
), ),
onPressed: () { onPressed: () {
// Void the order // Void the order
if (widget.table != null) { if (widget.table != null) {
final newTable = TableModel( final newTable = TableModel(
id: widget.table!.id, id: widget.table!.id,
tableName: widget tableName: widget.table!.tableName,
.table!.tableName,
status: 'available', status: 'available',
orderId: 0, orderId: 0,
paymentAmount: 0, paymentAmount: 0,
startTime: DateTime.now() startTime: DateTime.now().toIso8601String(),
.toIso8601String(), position: widget.table!.position,
position: widget
.table!.position,
); );
context context.read<StatusTableBloc>().add(
.read<StatusTableBloc>() StatusTableEvent.statusTabel(newTable),
.add(
StatusTableEvent
.statusTabel(
newTable),
); );
} }
// Remove draft order from local storage // Remove draft order from local storage
if (widget.draftOrder?.id != if (widget.draftOrder?.id != null) {
null) { ProductLocalDatasource.instance
ProductLocalDatasource .removeDraftOrderById(widget.draftOrder!.id!);
.instance
.removeDraftOrderById(
widget.draftOrder!
.id!);
} }
log("Voided order from payment page"); log("Voided order from payment page");
}, },
child: const Text( child: const Text(
"Ya, Batalkan", "Ya, Batalkan",
style: TextStyle( style: TextStyle(color: Colors.white),
color: Colors.white),
), ),
), ),
), ),
@ -1073,7 +1011,8 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
0, 0,
(previousValue, element) => (previousValue, element) =>
previousValue + previousValue +
(element.product.price! * (element.product.price!
.toIntegerFromText *
element.quantity), element.quantity),
), ),
); );
@ -1141,29 +1080,22 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
child: Button.filled( child: Button.filled(
onPressed: () async { onPressed: () async {
if (selectedPaymentMethod == null) { if (selectedPaymentMethod == null) {
ScaffoldMessenger.of(context) ScaffoldMessenger.of(context).showSnackBar(
.showSnackBar(
const SnackBar( const SnackBar(
content: Text( content: Text('Please select a payment method'),
'Please select a payment method'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
), ),
); );
return; return;
} }
final paymentMethodName = final paymentMethodName = selectedPaymentMethod?.name?.toLowerCase() ?? '';
selectedPaymentMethod?.name
?.toLowerCase() ??
'';
log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)"); log("Selected payment method: ${selectedPaymentMethod?.name} (normalized: $paymentMethodName)");
if (paymentMethodName == 'cash' || if (paymentMethodName == 'cash' ||
paymentMethodName == 'tunai' || paymentMethodName == 'tunai' ||
paymentMethodName == paymentMethodName == 'uang tunai' ||
'uang tunai' || paymentMethodName == 'cash payment') {
paymentMethodName ==
'cash payment') {
context.read<OrderBloc>().add( context.read<OrderBloc>().add(
OrderEvent.order( OrderEvent.order(
items, items,
@ -1177,9 +1109,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
widget.table?.id ?? 0, widget.table?.id ?? 0,
'completed', 'completed',
'paid', 'paid',
selectedPaymentMethod selectedPaymentMethod?.name ?? 'Cash',
?.name ??
'Cash',
totalPriceFinal, totalPriceFinal,
orderType)); orderType));
@ -1219,9 +1149,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
widget.table?.id ?? 0, widget.table?.id ?? 0,
'completed', 'completed',
'paid', 'paid',
selectedPaymentMethod selectedPaymentMethod?.name ?? 'Unknown Payment Method',
?.name ??
'Unknown Payment Method',
totalPriceFinal, totalPriceFinal,
orderType)); orderType));
@ -1245,7 +1173,7 @@ class _PaymentTablePageState extends State<PaymentTablePage> {
customerController.text, customerController.text,
), ),
); );
// Handle post-payment cleanup // Handle post-payment cleanup
await _handlePostPaymentCleanup(); await _handlePostPaymentCleanup();
} }

View File

@ -46,14 +46,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.13.0"
awesome_dio_interceptor:
dependency: "direct main"
description:
name: awesome_dio_interceptor
sha256: "4aef4adfdd9d8fda159870277b898a97986c6624baaf42f8a986d3130860d007"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
barcode: barcode:
dependency: transitive dependency: transitive
description: description:
@ -230,14 +222,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
colorize:
dependency: transitive
description:
name: colorize
sha256: "584746cd6ba1cba0633b6720f494fe6f9601c4170f0666c1579d2aa2a61071ba"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
connectivity_plus: connectivity_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -342,22 +326,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.2" version: "7.0.2"
dio:
dependency: "direct main"
description:
name: dio
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
url: "https://pub.dev"
source: hosted
version: "5.8.0+1"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
esc_pos_utils_plus: esc_pos_utils_plus:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -59,8 +59,6 @@ dependencies:
widgets_to_image: ^1.0.0 widgets_to_image: ^1.0.0
flutter_esc_pos_network: ^1.0.3 flutter_esc_pos_network: ^1.0.3
flutter_screenutil: ^5.9.3 flutter_screenutil: ^5.9.3
dio: ^5.8.0+1
awesome_dio_interceptor: ^1.3.0
# imin_printer: ^0.6.10 # imin_printer: ^0.6.10
dev_dependencies: dev_dependencies: