feat: syn product

This commit is contained in:
efrilm
2025-08-03 00:35:00 +07:00
parent 576f687d21
commit 7678b4791d
28 changed files with 1922 additions and 948 deletions
@@ -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,