diff --git a/lib/main.dart b/lib/main.dart index 1d07d16..a83d3a6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:cheminova/provider/collect_kyc_provider.dart'; +import 'package:cheminova/provider/product_provider.dart'; import 'package:cheminova/provider/user_provider.dart'; import 'package:cheminova/screens/splash_screen.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -96,6 +97,7 @@ Future main() async { providers: [ ChangeNotifierProvider(create: (context) => CollectKycProvider()), ChangeNotifierProvider(create: (_) => UserProvider()), + ChangeNotifierProvider(create: (_) => ProductProvider()), ], child: const MyApp(), ), diff --git a/lib/models/product_model.dart b/lib/models/product_model.dart new file mode 100644 index 0000000..d92262d --- /dev/null +++ b/lib/models/product_model.dart @@ -0,0 +1,181 @@ +class ProductResponse { + final bool success; + final int totalData; + final int totalPages; + final List product; + + ProductResponse({ + required this.success, + required this.totalData, + required this.totalPages, + required this.product, + }); + + factory ProductResponse.fromJson(Map json) { + return ProductResponse( + success: json['success'], + totalData: json['total_data'], + totalPages: json['total_pages'], + product: (json['product'] as List) + .map((item) => Product.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'success': success, + 'total_data': totalData, + 'total_pages': totalPages, + 'product': product.map((item) => item.toJson()).toList(), + }; + } +} + +class Product { + final String id; + final String SKU; + final String name; + final Category category; + final double price; + final GST gst; + final String description; + final String specialInstructions; + final String productStatus; + final AddedBy addedBy; + final List image; + final DateTime createdAt; + final DateTime updatedAt; + final int v; + + Product({ + required this.id, + required this.SKU, + required this.name, + required this.category, + required this.price, + required this.gst, + required this.description, + required this.specialInstructions, + required this.productStatus, + required this.addedBy, + required this.image, + required this.createdAt, + required this.updatedAt, + required this.v, + }); + + factory Product.fromJson(Map json) { + return Product( + id: json['_id'], + SKU: json['SKU'], + name: json['name'], + category: Category.fromJson(json['category']), + price: (json['price'] as num).toDouble(), + gst: GST.fromJson(json['GST']), + description: json['description'], + specialInstructions: json['special_instructions'], + productStatus: json['product_Status'], + addedBy: AddedBy.fromJson(json['addedBy']), + image: json['image'] as List, + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + v: json['__v'], + ); + } + + Map toJson() { + return { + '_id': id, + 'SKU': SKU, + 'name': name, + 'category': category.toJson(), + 'price': price, + 'GST': gst.toJson(), + 'description': description, + 'special_instructions': specialInstructions, + 'product_Status': productStatus, + 'addedBy': addedBy.toJson(), + 'image': image, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + '__v': v, + }; + } +} + +class Category { + final String id; + final String categoryName; + + Category({ + required this.id, + required this.categoryName, + }); + + factory Category.fromJson(Map json) { + return Category( + id: json['_id'], + categoryName: json['categoryName'], + ); + } + + Map toJson() { + return { + '_id': id, + 'categoryName': categoryName, + }; + } +} + +class GST { + final String id; + final String name; + final int tax; + + GST({ + required this.id, + required this.name, + required this.tax, + }); + + factory GST.fromJson(Map json) { + return GST( + id: json['_id'], + name: json['name'], + tax: json['tax'], + ); + } + + Map toJson() { + return { + '_id': id, + 'name': name, + 'tax': tax, + }; + } +} + +class AddedBy { + final String id; + final String name; + + AddedBy({ + required this.id, + required this.name, + }); + + factory AddedBy.fromJson(Map json) { + return AddedBy( + id: json['_id'], + name: json['name'], + ); + } + + Map toJson() { + return { + '_id': id, + 'name': name, + }; + } +} diff --git a/lib/provider/product_provider.dart b/lib/provider/product_provider.dart new file mode 100644 index 0000000..5db3785 --- /dev/null +++ b/lib/provider/product_provider.dart @@ -0,0 +1,40 @@ +import 'package:cheminova/models/product_model.dart'; +import 'package:cheminova/services/api_client.dart'; +import 'package:cheminova/services/api_urls.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:dio/dio.dart'; + +class ProductProvider extends ChangeNotifier { + ProductProvider() { + getProducts(); + } + + final _apiClient = ApiClient(); + ProductResponse? productResponse; + List productList = []; + + bool _isLoading = false; + + bool get isLoading => _isLoading; + + void setLoading(bool loading) { + _isLoading = loading; + notifyListeners(); + } + + Future getProducts() async { + setLoading(true); + try { + Response response = await _apiClient.get(ApiUrls.getProducts); + setLoading(false); + if (response.statusCode == 200) { + productResponse = ProductResponse.fromJson(response.data); + productList = productResponse!.product; + notifyListeners(); + } + } catch (e) { + setLoading(false); + print("Error: $e"); + } + } +} diff --git a/lib/screens/add_products_screen.dart b/lib/screens/add_products_screen.dart new file mode 100644 index 0000000..0b15924 --- /dev/null +++ b/lib/screens/add_products_screen.dart @@ -0,0 +1,296 @@ +import 'package:cheminova/models/product_model.dart'; +import 'package:cheminova/provider/product_provider.dart'; +import 'package:cheminova/screens/data_submit_successfull.dart'; +import 'package:cheminova/widgets/common_app_bar.dart'; +import 'package:cheminova/widgets/common_background.dart'; +import 'package:cheminova/widgets/common_drawer.dart'; +import 'package:cheminova/widgets/common_elevated_button.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class AddProductsScreen extends StatefulWidget { + const AddProductsScreen({super.key}); + + @override + State createState() => _AddProductsScreenState(); +} + +class _AddProductsScreenState extends State { + List selectedProducts = []; + List filteredProducts = []; + final searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + } + + void filterProducts(String query) { + setState(() { + final provider = Provider.of(context, listen: false); + filteredProducts = provider.productList.where((product) { + final productNameLower = product.name.toLowerCase(); + final productSkuLower = product.SKU.toLowerCase(); + final searchLower = query.toLowerCase(); + + return productNameLower.contains(searchLower) || + productSkuLower.contains(searchLower); + }).toList(); + }); + } + + @override + Widget build(BuildContext context) { + return CommonBackground( + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: CommonAppBar( + actions: [ + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Image.asset('assets/Back_attendance.png'), + padding: const EdgeInsets.only(right: 20), + ), + ], + title: const Text('Add Products', + style: TextStyle( + fontSize: 20, + color: Colors.black, + fontWeight: FontWeight.w400, + fontFamily: 'Anek')), + backgroundColor: Colors.transparent, + elevation: 0, + ), + drawer: const CommonDrawer(), + body: Consumer( + builder: (context, provider, child) { + if (provider.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + filteredProducts = provider.productList; + + return Stack( + children: [ + Column( + children: [ + if (selectedProducts.isNotEmpty) + Expanded( + child: ListView.builder( + itemCount: selectedProducts.length, + itemBuilder: (context, index) { + return ProductBlock( + product: selectedProducts[index]); + }, + ), + ), + ], + ), + Align( + alignment: selectedProducts.isEmpty + ? Alignment.center + : Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton.extended( + onPressed: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: searchController, + decoration: const InputDecoration( + labelText: + 'Search by name or SKU', + border: OutlineInputBorder(), + prefixIcon: Icon(Icons.search), + ), + onChanged: (value) { + filterProducts(value); + setState(() {}); + }, + ), + ), + Expanded( + child: ListView.builder( + itemCount: filteredProducts.length, + itemBuilder: (context, index) { + bool isAlreadySelected = + selectedProducts.contains( + filteredProducts[index]); + return Card( + child: ListTile( + title: Text( + filteredProducts[index] + .name, + style: TextStyle( + color: isAlreadySelected + ? Colors.grey + : Colors.black, + ), + ), + subtitle: Text( + filteredProducts[index].SKU, + style: TextStyle( + color: isAlreadySelected + ? Colors.grey + : Colors.black, + ), + ), + onTap: isAlreadySelected + ? null + : () { + setState(() { + selectedProducts.add( + filteredProducts[ + index]); + }); + Navigator.pop( + context); + }, + ), + ); + }, + ), + ), + ], + ); + }, + ); + }, + ).whenComplete(() { + setState(() {}); + }); + }, + backgroundColor: Colors.white, + icon: const Icon(Icons.add, color: Colors.black), + label: const Text( + 'Add Products', + style: TextStyle(color: Colors.black), + ), + ), + if (selectedProducts.isNotEmpty) ...[ + const SizedBox(height: 16.0), + CommonElevatedButton( + borderRadius: 30, + width: double.infinity, + height: kToolbarHeight - 10, + text: 'SUBMIT', + backgroundColor: const Color(0xff004791), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const DataSubmitSuccessfull(), + ), + ); + }, + ), + ], + ], + ), + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +class ProductBlock extends StatefulWidget { + final Product product; + + const ProductBlock({super.key, required this.product}); + + @override + _ProductBlockState createState() => _ProductBlockState(); +} + +class _ProductBlockState extends State { + final saleController = TextEditingController(); + final inventoryController = TextEditingController(); + String? errorMessage; + + @override + void initState() { + super.initState(); + } + + void validateInput() { + setState(() { + if (saleController.text.isNotEmpty && + inventoryController.text.isNotEmpty) { + int sale = int.parse(saleController.text); + int inventory = int.parse(inventoryController.text); + if (inventory > sale) { + errorMessage = 'Inventory should be less than or equal to sales'; + } else { + errorMessage = null; + } + } else { + errorMessage = null; + } + }); + } + + @override + Widget build(BuildContext context) { + return Card( + // color: !widget.product.isPurchased ? Colors.white54 : Colors.white, + color: Colors.white, + margin: const EdgeInsets.all(8), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Product: ${widget.product.name}', + style: const TextStyle(fontSize: 16)), + Text('SKU: ${widget.product.SKU}', + style: const TextStyle(fontSize: 15)), + const SizedBox(height: 8), + TextField( + controller: saleController, + decoration: const InputDecoration(labelText: 'Sale'), + keyboardType: TextInputType.number, + // enabled: widget.product.isPurchased, + enabled: true, + onChanged: (_) => validateInput(), + ), + TextField( + controller: inventoryController, + decoration: const InputDecoration(labelText: 'Inventory'), + keyboardType: TextInputType.number, + // enabled: widget.product.isPurchased, + enabled: true, + onChanged: (_) => validateInput(), + ), + if (errorMessage != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + errorMessage!, + style: const TextStyle(color: Colors.red), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/update_inventory_screen.dart b/lib/screens/update_inventory_screen.dart index e26862a..23b412c 100644 --- a/lib/screens/update_inventory_screen.dart +++ b/lib/screens/update_inventory_screen.dart @@ -1,9 +1,9 @@ +import 'package:cheminova/screens/Add_products_screen.dart'; import 'package:flutter/material.dart'; import 'package:cheminova/widgets/common_background.dart'; import 'package:cheminova/widgets/common_app_bar.dart'; import 'package:cheminova/widgets/common_drawer.dart'; import 'package:cheminova/widgets/common_elevated_button.dart'; -import 'package:cheminova/screens/data_submit_successfull.dart'; class UpdateInventoryScreen extends StatefulWidget { const UpdateInventoryScreen({super.key}); @@ -13,28 +13,14 @@ class UpdateInventoryScreen extends StatefulWidget { } class _UpdateInventoryScreenState extends State { - final searchController = TextEditingController(); - List products = [ - Product(name: 'Product A', sku: 'SKU001', isPurchased: true), - Product(name: 'Product B', sku: 'SKU002', isPurchased: true), - Product(name: 'Product C', sku: 'SKU003', isPurchased: false), - ]; - - List filteredProducts = []; + final List principalDistributors = ['vaibhav', 'sonu', 'monu']; + final List retailerDistributors = ['shivam', 'vivek']; + String? selectedDistributorType; + String? selectedDistributor; @override void initState() { super.initState(); - filteredProducts = products; - } - - void filterProducts(String query) { - setState(() { - filteredProducts = products - .where((product) => - product.name.toLowerCase().contains(query.toLowerCase())) - .toList(); - }); } @override @@ -62,155 +48,95 @@ class _UpdateInventoryScreenState extends State { elevation: 0, ), drawer: const CommonDrawer(), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: TextField( - controller: searchController, - decoration: const InputDecoration( - fillColor: Colors.white, - filled: true, - labelText: 'Search', - suffixIcon: Icon(Icons.search), - border: OutlineInputBorder(), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(16.0), + child: CommonElevatedButton( + borderRadius: 30, + width: double.infinity, + height: kToolbarHeight - 10, + text: 'SUBMIT', + backgroundColor: const Color(0xff004791), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AddProductsScreen(), ), - onChanged: filterProducts, - ), - ), - Expanded( - child: ListView.builder( - itemCount: filteredProducts.length, - itemBuilder: (context, index) { - return ProductBlock(product: filteredProducts[index]); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: CommonElevatedButton( - borderRadius: 30, - width: double.infinity, - height: kToolbarHeight - 10, - text: 'SUBMIT', - backgroundColor: const Color(0xff004791), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const DataSubmitSuccessfull(), + ); + }, + ), + ), + body: Stack( + children: [ + Column( + children: [ + // Dropdown for selecting distributor type + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15.0, vertical: 25), + child: DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'Select Distributor Type', + fillColor: Colors.white, + filled: true, + border: OutlineInputBorder(), ), - ); - }, - ), - ), - ], - ), - ), - ); - } -} - -class Product { - final String name; - final String sku; - final bool isPurchased; - int? sale; - int? inventory; - String? liquidation; - - Product({ - required this.name, - required this.sku, - required this.isPurchased, - this.sale, - this.inventory, - this.liquidation, - }); -} - -class ProductBlock extends StatefulWidget { - final Product product; - - const ProductBlock({super.key, required this.product}); - - @override - _ProductBlockState createState() => _ProductBlockState(); -} - -class _ProductBlockState extends State { - final saleController = TextEditingController(); - final inventoryController = TextEditingController(); - final liquidationController = TextEditingController(); - String? errorMessage; - - @override - void initState() { - super.initState(); - saleController.text = widget.product.sale?.toString() ?? ''; - inventoryController.text = widget.product.inventory?.toString() ?? ''; - liquidationController.text = widget.product.liquidation ?? ''; - } - - void validateInput() { - setState(() { - if (saleController.text.isNotEmpty && inventoryController.text.isNotEmpty) { - int sale = int.parse(saleController.text); - int inventory = int.parse(inventoryController.text); - if (inventory > sale) { - errorMessage = 'Inventory should be less than or equal to sales'; - } else { - errorMessage = null; - } - } else { - errorMessage = null; - } - }); - } - - @override - Widget build(BuildContext context) { - return Card( - color: !widget.product.isPurchased?Colors.white54:Colors.white, - margin: const EdgeInsets.all(8), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Product: ${widget.product.name}',style: const TextStyle(fontSize: 16),), - Text('SKU: ${widget.product.sku}',style: const TextStyle(fontSize: 15),), - const SizedBox(height: 8), - TextField( - controller: saleController, - decoration: const InputDecoration(labelText: 'Sale'), - keyboardType: TextInputType.number, - enabled: widget.product.isPurchased, - onChanged: (_) => validateInput(), - ), - TextField( - controller: inventoryController, - decoration: const InputDecoration(labelText: 'Inventory'), - keyboardType: TextInputType.number, - enabled: widget.product.isPurchased, - onChanged: (_) => validateInput(), - ), - TextField( - controller: liquidationController, - decoration: const InputDecoration(labelText: 'Liquidation'), - enabled: widget.product.isPurchased, - ), - if (errorMessage != null) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - errorMessage!, - style: const TextStyle(color: Colors.red), + value: selectedDistributorType, + items: ['Principal Distributor', 'Retailer Distributor'] + .map((String type) { + return DropdownMenuItem( + value: type, + child: Text(type), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedDistributorType = value; + selectedDistributor = + null; // Reset distributor selection when type changes + }); + }, + ), ), - ), + // Dropdown for selecting distributor name based on type + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15.0, vertical: 25), + child: DropdownButtonFormField( + decoration: const InputDecoration( + labelText: 'Select Distributor Name', + fillColor: Colors.white, + filled: true, + border: OutlineInputBorder(), + ), + value: selectedDistributor, + items: (selectedDistributorType == 'Principal Distributor' + ? principalDistributors + : retailerDistributors) + .map((String distributor) { + return DropdownMenuItem( + value: distributor, + child: Text(distributor), + ); + }).toList(), + onChanged: (value) { + setState(() { + selectedDistributor = value; + }); + }, + // Disable the dropdown if no distributor type is selected + isExpanded: true, + isDense: true, + iconSize: 24, + hint: Text( + 'Please select a ${selectedDistributorType ?? "Distributor Type"} first'), + ), + ), + ], + ), ], ), ), ); } -} \ No newline at end of file +} diff --git a/lib/services/api_urls.dart b/lib/services/api_urls.dart index c8c9470..7ce167a 100644 --- a/lib/services/api_urls.dart +++ b/lib/services/api_urls.dart @@ -11,4 +11,5 @@ class ApiUrls { static const String rejectedApplication = '${baseUrl}kyc/getAllrejected-tm'; static const String notificationUrl = '$baseUrl/get-notification-tm'; static const String fcmUrl = '${baseUrl}kyc/save-fcm-tm'; + static const String getProducts = '${baseUrl}product/getAll/user/'; } diff --git a/lib/widgets/common_elevated_button.dart b/lib/widgets/common_elevated_button.dart index 7e39473..d1089f9 100644 --- a/lib/widgets/common_elevated_button.dart +++ b/lib/widgets/common_elevated_button.dart @@ -36,7 +36,7 @@ class CommonElevatedButton extends StatelessWidget { ? const CircularProgressIndicator( backgroundColor: Colors.white, valueColor: AlwaysStoppedAnimation(Colors.black)) - : Text(text ?? 'Submit', + : Text(text, style: const TextStyle( fontSize: 15, color: Colors.white,