feat: form product

This commit is contained in:
efrilm 2025-08-01 14:47:24 +07:00
parent a1f3fbe854
commit 06bf0d7987

View File

@ -1,5 +1,6 @@
import 'dart:developer';
import 'package:enaklo_pos/core/components/custom_modal_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/components/custom_text_field.dart';
@ -21,10 +22,7 @@ import '../../../core/components/spaces.dart';
class FormProductDialog extends StatefulWidget {
final Product? product;
const FormProductDialog({
super.key,
this.product,
});
const FormProductDialog({super.key, this.product});
@override
State<FormProductDialog> createState() => _FormProductDialogState();
@ -54,7 +52,393 @@ class _FormProductDialogState extends State<FormProductDialog> {
// Check if we're in edit mode
isEditMode = widget.product != null;
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;
}
super.initState();
}
@override
void dispose() {
super.dispose();
nameController!.dispose();
priceController!.dispose();
stockController!.dispose();
}
@override
Widget build(BuildContext context) {
return CustomModalDialog(
title: widget.product == null ? "Tambah Produk" : "Ubah Produk",
subtitle: widget.product == null
? "Silakan isi formulir untuk menambahkan produk baru"
: "Silakan edit formulir untuk memperbarui produk",
child: Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomTextField(
controller: nameController!,
label: 'Nama Produk',
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.words,
),
const SpaceHeight(20.0),
CustomTextField(
controller: priceController!,
label: 'Harga',
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
onChanged: (value) {
priceValue = value.toIntegerFromText;
final int newValue = value.toIntegerFromText;
priceController!.text = newValue.currencyFormatRp;
priceController!.selection = TextSelection.fromPosition(
TextPosition(offset: priceController!.text.length));
},
),
const SpaceHeight(20.0),
ImagePickerWidget(
label: 'Foto Produk',
onChanged: (file) {
if (file == null) {
return;
}
imageFile = file;
},
initialImageUrl: imageUrl,
),
const SpaceHeight(20.0),
CustomTextField(
controller: stockController!,
label: 'Stok',
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}");
}
}
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",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
//radio printer type
const SpaceHeight(12.0),
Row(
children: [
Radio(
value: 'kitchen',
groupValue: printType,
onChanged: (value) {
setState(() {
printType = value;
});
},
),
const Text('Kitchen'),
],
),
Row(
children: [
Radio(
value: 'bar',
groupValue: printType,
onChanged: (value) {
setState(() {
printType = value;
});
},
),
const Text('Bar'),
],
),
const SpaceHeight(20.0),
Row(
children: [
Checkbox(
value: isBestSeller,
onChanged: (value) {
setState(() {
isBestSeller = value!;
});
},
),
const Text('Produk Favorit'),
],
),
const SpaceHeight(20.0),
const SpaceHeight(24.0),
if (isEditMode)
BlocConsumer<UpdateProductBloc, UpdateProductState>(
listener: (context, state) {
state.maybeMap(
orElse: () {},
success: (_) {
context
.read<SyncProductBloc>()
.add(const SyncProductEvent.syncProduct());
context
.read<GetProductsBloc>()
.add(const GetProductsEvent.fetch());
context.pop(true);
const snackBar = SnackBar(
content: Text('Success Update Product'),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context).showSnackBar(
snackBar,
);
},
error: (message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $message'),
backgroundColor: Colors.red,
),
);
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
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;
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,
);
context.read<UpdateProductBloc>().add(
UpdateProductEvent.updateProduct(
product, imageFile));
},
label: 'Ubah Produk',
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
)
else
BlocConsumer<AddProductBloc, AddProductState>(
listener: (context, state) {
state.maybeMap(
orElse: () {},
success: (_) {
context
.read<SyncProductBloc>()
.add(const SyncProductEvent.syncProduct());
context
.read<GetProductsBloc>()
.add(const GetProductsEvent.fetch());
context.pop(true);
const snackBar = SnackBar(
content: Text('Success Add Product'),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context).showSnackBar(
snackBar,
);
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
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;
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',
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
),
const SpaceHeight(16.0),
],
),
),
),
),
);
}
}
class FormProductDialogOld extends StatefulWidget {
final Product? product;
const FormProductDialogOld({
super.key,
this.product,
});
@override
State<FormProductDialogOld> createState() => _FormProductDialogOldState();
}
class _FormProductDialogOldState extends State<FormProductDialogOld> {
TextEditingController? nameController;
TextEditingController? priceController;
TextEditingController? stockController;
XFile? imageFile;
bool isBestSeller = false;
int priceValue = 0;
CategoryModel? selectCategory;
String? imageUrl;
String? printType = 'kitchen';
bool isEditMode = false;
@override
void initState() {
context.read<GetCategoriesBloc>().add(const GetCategoriesEvent.fetch());
nameController = TextEditingController();
priceController = TextEditingController();
stockController = TextEditingController();
// Check if we're in edit mode
isEditMode = widget.product != null;
if (isEditMode) {
// Pre-fill the form with existing product data
final product = widget.product!;
@ -158,7 +542,9 @@ class _FormProductDialogState extends State<FormProductDialog> {
},
success: (categories) {
// Set the selected category if in edit mode and not already set
if (isEditMode && selectCategory == null && widget.product?.category != null) {
if (isEditMode &&
selectCategory == null &&
widget.product?.category != null) {
try {
selectCategory = categories.firstWhere(
(cat) => cat.id == widget.product!.category!.id,
@ -169,7 +555,7 @@ class _FormProductDialogState extends State<FormProductDialog> {
log("No matching category found for product category ID: ${widget.product!.category!.id}");
}
}
return DropdownButtonHideUnderline(
child: Container(
decoration: BoxDecoration(
@ -304,11 +690,12 @@ class _FormProductDialogState extends State<FormProductDialog> {
);
return;
}
log("isBestSeller: $isBestSeller");
final String name = nameController!.text;
final int stock = stockController!.text.toIntegerFromText;
final int stock =
stockController!.text.toIntegerFromText;
final Product product = widget.product!.copyWith(
name: name,
price: priceValue.toString(),
@ -317,9 +704,10 @@ class _FormProductDialogState extends State<FormProductDialog> {
isFavorite: isBestSeller ? 1 : 0,
printerType: printType,
);
context.read<UpdateProductBloc>().add(
UpdateProductEvent.updateProduct(product, imageFile));
UpdateProductEvent.updateProduct(
product, imageFile));
},
label: 'Update Product',
);
@ -370,7 +758,7 @@ class _FormProductDialogState extends State<FormProductDialog> {
);
return;
}
log("isBestSeller: $isBestSeller");
final String name = nameController!.text;
@ -386,7 +774,8 @@ class _FormProductDialogState extends State<FormProductDialog> {
printerType: printType,
);
context.read<AddProductBloc>().add(
AddProductEvent.addProduct(product, imageFile!));
AddProductEvent.addProduct(
product, imageFile!));
},
label: 'Save Product',
);