diff --git a/lib/main.dart b/lib/main.dart index ec6879a..02b5c9b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:cheminova/provider/home_provider.dart'; +import 'package:cheminova/provider/products_provider.dart'; import 'package:cheminova/screens/splash_screen.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart'; @@ -89,6 +90,7 @@ Future main() async { runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (context) => HomeProvider()), + ChangeNotifierProvider(create: (context) => ProductProvider()), ], child: const MyApp())); } diff --git a/lib/models/products_response.dart b/lib/models/products_response.dart new file mode 100644 index 0000000..7ec633b --- /dev/null +++ b/lib/models/products_response.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, + }; + } +} \ No newline at end of file diff --git a/lib/provider/products_provider.dart b/lib/provider/products_provider.dart new file mode 100644 index 0000000..cab7127 --- /dev/null +++ b/lib/provider/products_provider.dart @@ -0,0 +1,41 @@ +import 'package:cheminova/services/api_client.dart'; +import 'package:cheminova/services/api_urls.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:dio/dio.dart'; + +import '../models/products_response.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"); + } + } +} \ No newline at end of file diff --git a/lib/screens/Add_products_screen.dart b/lib/screens/Add_products_screen.dart index 6b8b41e..45f46c3 100644 --- a/lib/screens/Add_products_screen.dart +++ b/lib/screens/Add_products_screen.dart @@ -1,9 +1,12 @@ -import 'package:flutter/material.dart'; -import 'package:cheminova/widgets/common_background.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:cheminova/screens/data_submit_successfull.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../models/products_response.dart'; +import '../provider/products_provider.dart'; class AddProductsScreen extends StatefulWidget { const AddProductsScreen({super.key}); @@ -13,27 +16,25 @@ class AddProductsScreen extends StatefulWidget { } class _AddProductsScreenState extends State { - final 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 selectedProducts = []; List filteredProducts = []; final searchController = TextEditingController(); + late ProductProvider provider; @override void initState() { super.initState(); - filteredProducts = products; + provider=Provider.of(context,listen: false); + provider.getProducts(); + filteredProducts = provider.productList; } void filterProducts(String query) { setState(() { - filteredProducts = products.where((product) { + final provider = Provider.of(context, listen: false); + filteredProducts = provider.productList.where((product) { final productNameLower = product.name.toLowerCase(); - final productSkuLower = product.sku.toLowerCase(); + final productSkuLower = product.SKU.toLowerCase(); final searchLower = query.toLowerCase(); return productNameLower.contains(searchLower) || @@ -44,166 +45,184 @@ class _AddProductsScreenState extends State { @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')), + + return + CommonBackground( + child: Scaffold( backgroundColor: Colors.transparent, - elevation: 0, - ), - drawer: const CommonDrawer(), - body: Stack( - children: [ - Column( - children: [ - if (selectedProducts.isNotEmpty) - Expanded( - child: ListView.builder( - itemCount: selectedProducts.length, - itemBuilder: (context, index) { - return ProductBlock(product: selectedProducts[index]); - }, - ), + 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()); + } + + 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 ListTile( - title: Text( - filteredProducts[index].name, - style: TextStyle( - color: isAlreadySelected ? Colors.grey : Colors.black, + 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( + isScrollControlled: true, + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context).size.height * 0.9, + ), + context: context, + builder: (BuildContext context) { + return Consumer(builder: (context, value, child) =>StatefulBuilder( + builder: (context, setState) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(18.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(() {}); + }, ), ), - subtitle: Text( - filteredProducts[index].sku, - style: TextStyle( - color: isAlreadySelected ? Colors.grey : Colors.black, + 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); + }, + ), + ); + }, ), ), - 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(), + ), ); }, - ); - }, - ).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 Product { - final String name; - final String sku; - final bool isPurchased; - int? sale; - int? inventory; - - Product({ - required this.name, - required this.sku, - required this.isPurchased, - this.sale, - this.inventory, - }); -} - class ProductBlock extends StatefulWidget { final Product product; @@ -221,13 +240,12 @@ class _ProductBlockState extends State { @override void initState() { super.initState(); - saleController.text = widget.product.sale?.toString() ?? ''; - inventoryController.text = widget.product.inventory?.toString() ?? ''; } void validateInput() { setState(() { - if (saleController.text.isNotEmpty && inventoryController.text.isNotEmpty) { + if (saleController.text.isNotEmpty && + inventoryController.text.isNotEmpty) { int sale = int.parse(saleController.text); int inventory = int.parse(inventoryController.text); if (inventory > sale) { @@ -244,28 +262,33 @@ class _ProductBlockState extends State { @override Widget build(BuildContext context) { return Card( - color: !widget.product.isPurchased ? Colors.white54 : Colors.white, + // 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)), + 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: widget.product.isPurchased, + enabled: true, onChanged: (_) => validateInput(), ), TextField( controller: inventoryController, decoration: const InputDecoration(labelText: 'Inventory'), keyboardType: TextInputType.number, - enabled: widget.product.isPurchased, + // enabled: widget.product.isPurchased, + enabled: true, onChanged: (_) => validateInput(), ), if (errorMessage != null) @@ -281,4 +304,4 @@ class _ProductBlockState extends State { ), ); } -} +} \ No newline at end of file diff --git a/lib/services/api_urls.dart b/lib/services/api_urls.dart index cfe3377..6758bb0 100644 --- a/lib/services/api_urls.dart +++ b/lib/services/api_urls.dart @@ -13,4 +13,6 @@ class ApiUrls { static const String rejectedApplication = '${baseUrl}kyc/getAllrejected'; static const String notificationUrl = '${baseUrl}/get-notification-sc'; static const String fcmUrl = '${baseUrl}kyc/save-fcm-sc'; + 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 054cc4c..357bfd5 100644 --- a/lib/widgets/common_elevated_button.dart +++ b/lib/widgets/common_elevated_button.dart @@ -24,7 +24,7 @@ class CommonElevatedButton extends StatelessWidget { height: height ?? kToolbarHeight - 25, width: width ?? 200, child: ElevatedButton( - onPressed: onPressed, + onPressed: isLoading ? null : onPressed, style: ElevatedButton.styleFrom( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(borderRadius ?? 6)),