feat: syn product
This commit is contained in:
parent
576f687d21
commit
7678b4791d
@ -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(
|
||||
|
||||
@ -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} ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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> {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>(
|
||||
|
||||
@ -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 ?? [])),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
part of 'product_loader_bloc.dart';
|
||||
|
||||
@freezed
|
||||
class ProductLoaderEvent with _$ProductLoaderEvent {
|
||||
const factory ProductLoaderEvent.getProduct() = _GetProduct;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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),
|
||||
);
|
||||
|
||||
@ -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),
|
||||
),
|
||||
);
|
||||
|
||||
@ -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',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
),
|
||||
|
||||
@ -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()}");
|
||||
|
||||
@ -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!));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@ -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> {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
),
|
||||
|
||||
@ -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',
|
||||
);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user