feat: syn product

This commit is contained in:
efrilm 2025-08-03 00:35:00 +07:00
parent 576f687d21
commit 7678b4791d
28 changed files with 1922 additions and 948 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import 'dart:developer';
import 'package:bloc/bloc.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/order_remote_datasource.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
@ -33,8 +32,7 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
final subTotal = event.items.fold<int>(
0,
(previousValue, element) =>
previousValue +
(element.product.price!.toIntegerFromText * element.quantity));
previousValue + (element.product.price! * element.quantity));
// final total = subTotal + event.tax + event.serviceCharge - event.discount;
final totalItem = event.items.fold<int>(

View File

@ -0,0 +1,23 @@
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

@ -0,0 +1,790 @@
// 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

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,13 @@
// 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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/string_ext.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/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 '../../../core/assets/assets.gen.dart';
@ -49,12 +46,15 @@ class _HomePageState extends State<HomePage> {
void _syncAndLoadProducts() {
// 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
// context
// .read<LocalProductBloc>()
// .add(const LocalProductEvent.getLocalProduct());
context
.read<LocalProductBloc>()
.add(const LocalProductEvent.getLocalProduct());
.read<ProductLoaderBloc>()
.add(const ProductLoaderEvent.getProduct());
// Initialize checkout with tax and service charge settings
context.read<CheckoutBloc>().add(const CheckoutEvent.started());
@ -83,7 +83,7 @@ class _HomePageState extends State<HomePage> {
List<Product> products, int categoryId) {
final filteredBySearch = _filterProducts(products);
return filteredBySearch
.where((element) => element.category?.id == categoryId)
.where((element) => element.price == categoryId)
.toList();
}
@ -93,451 +93,420 @@ class _HomePageState extends State<HomePage> {
tag: 'confirmation_screen',
child: Scaffold(
backgroundColor: AppColors.white,
body: BlocListener<SyncProductBloc, SyncProductState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
error: (message) {
// If sync fails, still try to load local products
context
.read<LocalProductBloc>()
.add(const LocalProductEvent.getLocalProduct());
},
loaded: (productResponseModel) async {
// Store context reference before async operations
final localProductBloc = context.read<LocalProductBloc>();
// Save synced products to local database
await ProductLocalDatasource.instance.deleteAllProducts();
await ProductLocalDatasource.instance.insertProducts(
productResponseModel.data!,
);
// 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: () {
body: 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<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: CircularProgressIndicator(),
child: Text('No Items Found'),
);
}, loading: () {
}
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: CircularProgressIndicator(),
child: Text('No Items'),
);
}, 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: () {
}
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: CircularProgressIndicator(),
child: Text('No Items'),
);
}, 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(
}
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: () {},
),
);
}),
),
// Minuman Tab
SizedBox(
child: state.maybeWhen(orElse: () {
);
},
);
}),
),
// 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: CircularProgressIndicator(),
child: Text('No Items'),
);
}, 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: () {},
);
},
);
}),
),
],
),
);
},
),
],
),
}
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',
),
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(
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,
),
),
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),
const Divider(),
const SpaceHeight(8.0),
],
),
),
Expanded(
child: SingleChildScrollView(
padding:
const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
),
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) {
return state.maybeWhen(
orElse: () => const Center(
child: Text('No Items'),
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,
),
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',
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(
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,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/presentation/home/bloc/checkout/checkout_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
@ -52,7 +51,7 @@ class _OrderMenuState extends State<OrderMenu> {
child: ListTile(
contentPadding: EdgeInsets.zero,
leading: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
child:
// Icon(
// Icons.fastfood,
@ -60,14 +59,23 @@ class _OrderMenuState extends State<OrderMenu> {
// color: AppColors.primary,
// ),
CachedNetworkImage(
imageUrl: widget.data.product.image!.contains('http')
? widget.data.product.image!
: '${Variables.baseUrl}/${widget.data.product.image}',
imageUrl: widget.data.product.name!.contains('http')
? widget.data.product.name!
: '${Variables.baseUrl}/${widget.data.product.name}',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
errorWidget: (context, url, error) =>
const Icon(Icons.error),
errorWidget: (context, url, error) => Container(
width: 50.0,
height: 50.0,
decoration: BoxDecoration(
color: AppColors.disabled.withOpacity(0.4),
),
child: const Icon(
Icons.image,
color: AppColors.grey,
),
),
),
),
title: Row(
@ -86,8 +94,7 @@ class _OrderMenuState extends State<OrderMenu> {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.data.product.price!.toIntegerFromText
.currencyFormatRp),
Text(widget.data.product.price!.currencyFormatRp),
],
),
),
@ -139,8 +146,7 @@ class _OrderMenuState extends State<OrderMenu> {
SizedBox(
width: 80.0,
child: Text(
(widget.data.product.price!.toIntegerFromText *
widget.data.quantity)
(widget.data.product.price! * widget.data.quantity)
.currencyFormatRp,
textAlign: TextAlign.right,
style: const TextStyle(

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
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';
@ -19,7 +18,7 @@ class GetProductsBloc extends Bloc<GetProductsEvent, GetProductsState> {
response.fold(
(l) => emit(_Error(l)),
(r) {
emit(_Success(r.data!));
emit(_Success(r.data!.products!));
},
);
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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