From 06bf0d7987e62e4646bb15280ec102e622e207e4 Mon Sep 17 00:00:00 2001 From: efrilm Date: Fri, 1 Aug 2025 14:47:24 +0700 Subject: [PATCH] feat: form product --- .../setting/dialogs/form_product_dialog.dart | 417 +++++++++++++++++- 1 file changed, 403 insertions(+), 14 deletions(-) diff --git a/lib/presentation/setting/dialogs/form_product_dialog.dart b/lib/presentation/setting/dialogs/form_product_dialog.dart index 2c4a3f3..2f1fa5b 100644 --- a/lib/presentation/setting/dialogs/form_product_dialog.dart +++ b/lib/presentation/setting/dialogs/form_product_dialog.dart @@ -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 createState() => _FormProductDialogState(); @@ -54,7 +52,393 @@ class _FormProductDialogState extends State { // 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( + 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( + 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>( + (CategoryModel category) { + return DropdownMenuItem( + 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( + listener: (context, state) { + state.maybeMap( + orElse: () {}, + success: (_) { + context + .read() + .add(const SyncProductEvent.syncProduct()); + context + .read() + .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().add( + UpdateProductEvent.updateProduct( + product, imageFile)); + }, + label: 'Ubah Produk', + ); + }, + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, + ); + }, + ) + else + BlocConsumer( + listener: (context, state) { + state.maybeMap( + orElse: () {}, + success: (_) { + context + .read() + .add(const SyncProductEvent.syncProduct()); + context + .read() + .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().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 createState() => _FormProductDialogOldState(); +} + +class _FormProductDialogOldState extends State { + 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().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 { }, 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 { 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 { ); 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 { isFavorite: isBestSeller ? 1 : 0, printerType: printType, ); - + context.read().add( - UpdateProductEvent.updateProduct(product, imageFile)); + UpdateProductEvent.updateProduct( + product, imageFile)); }, label: 'Update Product', ); @@ -370,7 +758,7 @@ class _FormProductDialogState extends State { ); return; } - + log("isBestSeller: $isBestSeller"); final String name = nameController!.text; @@ -386,7 +774,8 @@ class _FormProductDialogState extends State { printerType: printType, ); context.read().add( - AddProductEvent.addProduct(product, imageFile!)); + AddProductEvent.addProduct( + product, imageFile!)); }, label: 'Save Product', );