From c5ca9acdac0480da3cb0e765d3dfda30844fb1e2 Mon Sep 17 00:00:00 2001 From: Hadi Mottale Date: Tue, 8 Jul 2025 12:17:29 +0330 Subject: [PATCH] Update: lib/screens/add_edit_product_screen.dart --- lib/screens/add_edit_product_screen.dart | 361 ++++++++++++++++++----- 1 file changed, 288 insertions(+), 73 deletions(-) diff --git a/lib/screens/add_edit_product_screen.dart b/lib/screens/add_edit_product_screen.dart index c1ac78d..fd7414b 100644 --- a/lib/screens/add_edit_product_screen.dart +++ b/lib/screens/add_edit_product_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import '../models/product.dart'; import '../services/database_helper.dart'; @@ -18,6 +19,30 @@ class _AddEditProductScreenState extends State { late TextEditingController _quantityController; late TextEditingController _priceController; late TextEditingController _descriptionController; + + late TextEditingController _imageUrlController; + late TextEditingController _serialNumberController; + late TextEditingController _modelController; + late TextEditingController _tagsController; + + late TextEditingController _costPriceController; + late TextEditingController _taxRateController; + late TextEditingController _discountController; + late TextEditingController _profitMarginController; + + late TextEditingController _unitOfMeasureController; + late TextEditingController _locationController; + late TextEditingController _minimumStockController; + late TextEditingController _supplierController; + + late TextEditingController _entryDateController; + late TextEditingController _manufactureDateController; + late TextEditingController _expirationDateController; + late TextEditingController _statusController; + + late TextEditingController _categoryController; + late TextEditingController _brandController; + late DatabaseHelper _databaseHelper; bool get isEditing => widget.product != null; @@ -26,17 +51,37 @@ class _AddEditProductScreenState extends State { void initState() { super.initState(); _databaseHelper = DatabaseHelper(); + _nameController = TextEditingController(text: widget.product?.name); _codeController = TextEditingController(text: widget.product?.code); - _quantityController = TextEditingController( - text: widget.product?.quantity.toString(), - ); - _priceController = TextEditingController( - text: widget.product?.price.toString(), - ); - _descriptionController = TextEditingController( - text: widget.product?.description, - ); + _descriptionController = TextEditingController(text: widget.product?.description); + _imageUrlController = TextEditingController(text: widget.product?.imageUrl); + _serialNumberController = TextEditingController(text: widget.product?.serialNumber); + _modelController = TextEditingController(text: widget.product?.model); + _tagsController = TextEditingController(text: widget.product?.tags?.join(', ')); + + _priceController = TextEditingController(text: widget.product?.price.toString()); + _costPriceController = TextEditingController(text: widget.product?.costPrice?.toString()); + _taxRateController = TextEditingController(text: widget.product?.taxRate?.toString()); + _discountController = TextEditingController(text: widget.product?.discount?.toString()); + _profitMarginController = TextEditingController(text: widget.product?.profitMargin?.toString()); + + _quantityController = TextEditingController(text: widget.product?.quantity.toString()); + _unitOfMeasureController = TextEditingController(text: widget.product?.unitOfMeasure); + _locationController = TextEditingController(text: widget.product?.location); + _minimumStockController = TextEditingController(text: widget.product?.minimumStock?.toString()); + _supplierController = TextEditingController(text: widget.product?.supplier); + + _entryDateController = TextEditingController( + text: widget.product?.entryDate != null ? DateFormat('yyyy-MM-dd').format(widget.product!.entryDate!) : ''); + _manufactureDateController = TextEditingController( + text: widget.product?.manufactureDate != null ? DateFormat('yyyy-MM-dd').format(widget.product!.manufactureDate!) : ''); + _expirationDateController = TextEditingController( + text: widget.product?.expirationDate != null ? DateFormat('yyyy-MM-dd').format(widget.product!.expirationDate!) : ''); + _statusController = TextEditingController(text: widget.product?.status); + + _categoryController = TextEditingController(text: widget.product?.category); + _brandController = TextEditingController(text: widget.product?.brand); } @override @@ -46,9 +91,47 @@ class _AddEditProductScreenState extends State { _quantityController.dispose(); _priceController.dispose(); _descriptionController.dispose(); + + _imageUrlController.dispose(); + _serialNumberController.dispose(); + _modelController.dispose(); + _tagsController.dispose(); + + _costPriceController.dispose(); + _taxRateController.dispose(); + _discountController.dispose(); + _profitMarginController.dispose(); + + _unitOfMeasureController.dispose(); + _locationController.dispose(); + _minimumStockController.dispose(); + _supplierController.dispose(); + + _entryDateController.dispose(); + _manufactureDateController.dispose(); + _expirationDateController.dispose(); + _statusController.dispose(); + + _categoryController.dispose(); + _brandController.dispose(); + super.dispose(); } + Future _selectDate(TextEditingController controller) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(2000), + lastDate: DateTime(2101), + ); + if (picked != null) { + setState(() { + controller.text = DateFormat('yyyy-MM-dd').format(picked); + }); + } + } + Future _saveProduct() async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); @@ -57,12 +140,36 @@ class _AddEditProductScreenState extends State { id: isEditing ? widget.product!.id : null, name: _nameController.text, code: _codeController.text, - quantity: int.parse(_quantityController.text), + description: _descriptionController.text.isEmpty ? null : _descriptionController.text, + imageUrl: _imageUrlController.text.isEmpty ? null : _imageUrlController.text, + serialNumber: _serialNumberController.text.isEmpty ? null : _serialNumberController.text, + model: _modelController.text.isEmpty ? null : _modelController.text, + tags: _tagsController.text.isEmpty ? null : _tagsController.text.split(',').map((e) => e.trim()).toList(), + price: double.parse(_priceController.text), - description: - _descriptionController.text.isEmpty - ? null - : _descriptionController.text, + costPrice: double.tryParse(_costPriceController.text), + taxRate: double.tryParse(_taxRateController.text), + discount: double.tryParse(_discountController.text), + profitMargin: double.tryParse(_profitMarginController.text), + + quantity: int.parse(_quantityController.text), + unitOfMeasure: _unitOfMeasureController.text.isEmpty ? null : _unitOfMeasureController.text, + location: _locationController.text.isEmpty ? null : _locationController.text, + minimumStock: int.tryParse(_minimumStockController.text), + supplier: _supplierController.text.isEmpty ? null : _supplierController.text, + + entryDate: _entryDateController.text.isNotEmpty ? DateTime.parse(_entryDateController.text) : null, + manufactureDate: _manufactureDateController.text.isNotEmpty ? DateTime.parse(_manufactureDateController.text) : null, + expirationDate: _expirationDateController.text.isNotEmpty ? DateTime.parse(_expirationDateController.text) : null, + status: _statusController.text.isEmpty ? null : _statusController.text, + + category: _categoryController.text.isEmpty ? null : _categoryController.text, + brand: _brandController.text.isEmpty ? null : _brandController.text, + + createdBy: isEditing ? widget.product?.createdBy : null, + createdAt: isEditing ? widget.product?.createdAt : null, + lastModifiedBy: isEditing ? widget.product?.lastModifiedBy : null, + lastModifiedAt: isEditing ? widget.product?.lastModifiedAt : null, ); if (isEditing) { @@ -89,74 +196,182 @@ class _AddEditProductScreenState extends State { key: _formKey, child: ListView( children: [ - TextFormField( - controller: _nameController, - decoration: const InputDecoration(labelText: 'نام کالا'), - validator: (value) { - if (value == null || value.isEmpty) { - return 'لطفا نام کالا را وارد کنید'; - } - return null; - }, - ), - const SizedBox(height: 10), - TextFormField( - controller: _codeController, - decoration: const InputDecoration(labelText: 'کد کالا'), - validator: (value) { - if (value == null || value.isEmpty) { - return 'لطفا کد کالا را وارد کنید'; - } - return null; - }, - ), - const SizedBox(height: 10), - TextFormField( - controller: _quantityController, - decoration: const InputDecoration(labelText: 'تعداد'), - keyboardType: TextInputType.number, - validator: (value) { - if (value == null || value.isEmpty) { - return 'لطفا تعداد را وارد کنید'; - } - if (int.tryParse(value) == null) { - return 'تعداد باید عدد باشد'; - } - return null; - }, - ), - const SizedBox(height: 10), - TextFormField( - controller: _priceController, - decoration: const InputDecoration(labelText: 'قیمت'), - keyboardType: TextInputType.number, - validator: (value) { - if (value == null || value.isEmpty) { - return 'لطفا قیمت را وارد کنید'; - } - if (double.tryParse(value) == null) { - return 'قیمت باید عدد باشد'; - } - return null; - }, - ), - const SizedBox(height: 10), - TextFormField( - controller: _descriptionController, - decoration: const InputDecoration( + _buildSectionTitle('مشخصات شناسایی و ظاهری'), + _buildTextFormField( + controller: _nameController, + labelText: 'نام کالا', + validator: (value) => value!.isEmpty ? 'لطفا نام کالا را وارد کنید' : null), + _buildTextFormField( + controller: _codeController, + labelText: 'کد کالا', + validator: (value) => value!.isEmpty ? 'لطفا کد کالا را وارد کنید' : null), + _buildTextFormField( + controller: _descriptionController, labelText: 'توضیحات (اختیاری)', - ), - maxLines: 3, - ), + maxLines: 3), + _buildTextFormField( + controller: _imageUrlController, + labelText: 'آدرس عکس کالا (URL/مسیر)', + keyboardType: TextInputType.url), + _buildTextFormField( + controller: _serialNumberController, + labelText: 'شماره سریال (اختیاری)'), + _buildTextFormField( + controller: _modelController, + labelText: 'شماره مدل (اختیاری)'), + _buildTextFormField( + controller: _tagsController, + labelText: 'هشتگ‌ها (با کاما جدا کنید)', + hintText: 'مثال: لوازم، برقی، آشپزخانه'), const SizedBox(height: 20), + + _buildSectionTitle('اطلاعات مالی و تجاری'), + _buildTextFormField( + controller: _priceController, + labelText: 'قیمت فروش (تومان)', + keyboardType: TextInputType.number, + validator: (value) => value!.isEmpty || double.tryParse(value) == null ? 'لطفا قیمت معتبر وارد کنید' : null), + _buildTextFormField( + controller: _costPriceController, + labelText: 'قیمت خرید / بهای تمام شده (اختیاری)', + keyboardType: TextInputType.number), + _buildTextFormField( + controller: _taxRateController, + labelText: 'نرخ مالیات (مثلاً 0.09 برای 9%) (اختیاری)', + keyboardType: TextInputType.number), + _buildTextFormField( + controller: _discountController, + labelText: 'میزان تخفیف (درصد یا مبلغ) (اختیاری)', + keyboardType: TextInputType.number), + _buildTextFormField( + controller: _profitMarginController, + labelText: 'درصد سود مورد انتظار (اختیاری)', + keyboardType: TextInputType.number), + const SizedBox(height: 20), + + _buildSectionTitle('موجودی و لجستیک'), + _buildTextFormField( + controller: _quantityController, + labelText: 'تعداد', + keyboardType: TextInputType.number, + validator: (value) => value!.isEmpty || int.tryParse(value) == null ? 'لطفا تعداد معتبر وارد کنید' : null), + _buildTextFormField( + controller: _unitOfMeasureController, + labelText: 'واحد اندازه‌گیری (مثلاً عدد، کیلوگرم) (اختیاری)'), + _buildTextFormField( + controller: _locationController, + labelText: 'مکان فیزیکی در انبار (اختیاری)'), + _buildTextFormField( + controller: _minimumStockController, + labelText: 'حداقل موجودی برای سفارش مجدد (اختیاری)', + keyboardType: TextInputType.number), + _buildTextFormField( + controller: _supplierController, + labelText: 'تامین‌کننده (اختیاری)'), + const SizedBox(height: 20), + + _buildSectionTitle('تاریخ‌گذاری و وضعیت'), + _buildDateField( + controller: _entryDateController, + labelText: 'تاریخ ورود/ثبت (اختیاری)'), + _buildDateField( + controller: _manufactureDateController, + labelText: 'تاریخ تولید (اختیاری)'), + _buildDateField( + controller: _expirationDateController, + labelText: 'تاریخ انقضا (اختیاری)'), + _buildTextFormField( + controller: _statusController, + labelText: 'وضعیت کالا (مثلاً موجود، ناموجود) (اختیاری)'), + const SizedBox(height: 20), + + _buildSectionTitle('دسته‌بندی و طبقه‌بندی'), + _buildTextFormField( + controller: _categoryController, + labelText: 'دسته بندی (مثلاً الکترونیک) (اختیاری)'), + _buildTextFormField( + controller: _brandController, + labelText: 'برند/شرکت سازنده (اختیاری)'), + const SizedBox(height: 20), + ElevatedButton( onPressed: _saveProduct, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12), + textStyle: const TextStyle(fontSize: 18), + ), child: Text(isEditing ? 'ذخیره تغییرات' : 'افزودن کالا'), ), + const SizedBox(height: 20), ], ), ), ), ); } -} + + Widget _buildTextFormField({ + required TextEditingController controller, + required String labelText, + String? Function(String?)? validator, + TextInputType keyboardType = TextInputType.text, + int maxLines = 1, + String? hintText, + bool readOnly = false, + }) { + return Column( + children: [ + TextFormField( + controller: controller, + decoration: InputDecoration( + labelText: labelText, + hintText: hintText, + border: const OutlineInputBorder(), + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + ), + keyboardType: keyboardType, + maxLines: maxLines, + validator: validator, + readOnly: readOnly, + ), + const SizedBox(height: 10), + ], + ); + } + + Widget _buildDateField({ + required TextEditingController controller, + required String labelText, + }) { + return Column( + children: [ + TextFormField( + controller: controller, + decoration: InputDecoration( + labelText: labelText, + border: const OutlineInputBorder(), + suffixIcon: const Icon(Icons.calendar_today), + contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + ), + readOnly: true, + onTap: () => _selectDate(controller), + ), + const SizedBox(height: 10), + ], + ); + } + + Widget _buildSectionTitle(String title) { + return Padding( + padding: const EdgeInsets.only(bottom: 10, top: 10), + child: Text( + title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.blueGrey, + ), + ), + ); + } +} \ No newline at end of file