From d734bbb6eef6077cc41048a04d7411aaa6fffb9a Mon Sep 17 00:00:00 2001 From: Vaibhav Date: Fri, 16 Aug 2024 17:54:32 +0530 Subject: [PATCH] API of products and PD/RD & Validations --- lib/constants/constant.dart | 3 + lib/main.dart | 3 +- lib/models/get_pd_rd_response.dart | 18 + lib/models/products_response.dart | 265 ++++++----- lib/provider/collect_kyc_provider.dart | 2 +- lib/provider/pd_rd_provider.dart | 73 ++++ lib/provider/products_provider.dart | 106 ++++- lib/screens/Add_products_screen.dart | 533 +++++++++++++---------- lib/screens/data_submit_successfull.dart | 8 +- lib/screens/display_sales_screen.dart | 2 +- lib/screens/home_screen.dart | 15 +- lib/screens/product_sales_data.dart | 4 +- lib/screens/summary_screen.dart | 2 +- lib/screens/update_inventory_screen.dart | 240 ++++------ lib/services/api_client.dart | 2 + lib/services/api_urls.dart | 3 +- 16 files changed, 720 insertions(+), 559 deletions(-) create mode 100644 lib/models/get_pd_rd_response.dart create mode 100644 lib/provider/pd_rd_provider.dart diff --git a/lib/constants/constant.dart b/lib/constants/constant.dart index 1d6839a..df0f43b 100644 --- a/lib/constants/constant.dart +++ b/lib/constants/constant.dart @@ -8,3 +8,6 @@ final GlobalKey _scaffoldMessengerKey = GlobalKey get scaffoldMessengerKey => _scaffoldMessengerKey; + + GlobalKey navigatorKey = GlobalKey(); + diff --git a/lib/main.dart b/lib/main.dart index 02b5c9b..d6b9315 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:developer'; import 'dart:io'; +import 'package:cheminova/constants/constant.dart'; import 'package:cheminova/provider/home_provider.dart'; import 'package:cheminova/provider/products_provider.dart'; import 'package:cheminova/screens/splash_screen.dart'; @@ -106,7 +107,7 @@ class _MyAppState extends State { Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, - // scaffoldMessengerKey: SnackBarService().scaffoldMessengerKey, + navigatorKey: navigatorKey, title: 'cheminova', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), diff --git a/lib/models/get_pd_rd_response.dart b/lib/models/get_pd_rd_response.dart new file mode 100644 index 0000000..6dd85b2 --- /dev/null +++ b/lib/models/get_pd_rd_response.dart @@ -0,0 +1,18 @@ +class GetPdRdResponse { + String? sId; + String? name; + + GetPdRdResponse({this.sId, this.name}); + + GetPdRdResponse.fromJson(Map json) { + sId = json['_id']; + name = json['name']; + } + + Map toJson() { + final Map data = {}; + data['_id'] = sId; + data['name'] = name; + return data; + } +} diff --git a/lib/models/products_response.dart b/lib/models/products_response.dart index 7ec633b..3f0a2e6 100644 --- a/lib/models/products_response.dart +++ b/lib/models/products_response.dart @@ -1,181 +1,168 @@ class ProductResponse { - final bool success; - final int totalData; - final int totalPages; - final List product; + bool? success; + int? totalData; + int? totalPages; + List? products; - ProductResponse({ - required this.success, - required this.totalData, - required this.totalPages, - required this.product, - }); + ProductResponse( + {this.success, this.totalData, this.totalPages, this.products}); - 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(), - ); + ProductResponse.fromJson(Map json) { + success = json['success']; + totalData = json['total_data']; + totalPages = json['total_pages']; + if (json['products'] != null) { + products = []; + json['products'].forEach((v) { + products!.add(new Products.fromJson(v)); + }); + } } Map toJson() { - return { - 'success': success, - 'total_data': totalData, - 'total_pages': totalPages, - 'product': product.map((item) => item.toJson()).toList(), - }; + final Map data = new Map(); + data['success'] = this.success; + data['total_data'] = this.totalData; + data['total_pages'] = this.totalPages; + if (this.products != null) { + data['products'] = this.products!.map((v) => v.toJson()).toList(); + } + return data; } } -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; +class Products { + String? sId; + String? sKU; + String? name; + Category? category; + int? price; + GST? gST; + String? description; + String? specialInstructions; + String? productStatus; + AddedBy? addedBy; + List? image; + String? createdAt; + String? updatedAt; + int? iV; - 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, - }); + Products( + {this.sId, + this.sKU, + this.name, + this.category, + this.price, + this.gST, + this.description, + this.specialInstructions, + this.productStatus, + this.addedBy, + this.image, + this.createdAt, + this.updatedAt, + this.iV}); - 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'], - ); + Products.fromJson(Map json) { + sId = json['_id']; + sKU = json['SKU']; + name = json['name']; + category = json['category'] != null + ? new Category.fromJson(json['category']) + : null; + price = json['price']; + gST = json['GST'] != null ? new GST.fromJson(json['GST']) : null; + description = json['description']; + specialInstructions = json['special_instructions']; + productStatus = json['product_Status']; + addedBy = + json['addedBy'] != null ? new AddedBy.fromJson(json['addedBy']) : null; + createdAt = json['createdAt']; + updatedAt = json['updatedAt']; + iV = 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, - }; + final Map data = new Map(); + data['_id'] = this.sId; + data['SKU'] = this.sKU; + data['name'] = this.name; + if (this.category != null) { + data['category'] = this.category!.toJson(); + } + data['price'] = this.price; + if (this.gST != null) { + data['GST'] = this.gST!.toJson(); + } + data['description'] = this.description; + data['special_instructions'] = this.specialInstructions; + data['product_Status'] = this.productStatus; + if (this.addedBy != null) { + data['addedBy'] = this.addedBy!.toJson(); + } + data['createdAt'] = this.createdAt; + data['updatedAt'] = this.updatedAt; + data['__v'] = this.iV; + return data; } } class Category { - final String id; - final String categoryName; + String? sId; + String? categoryName; - Category({ - required this.id, - required this.categoryName, - }); + Category({this.sId, this.categoryName}); - factory Category.fromJson(Map json) { - return Category( - id: json['_id'], - categoryName: json['categoryName'], - ); + Category.fromJson(Map json) { + sId = json['_id']; + categoryName = json['categoryName']; } Map toJson() { - return { - '_id': id, - 'categoryName': categoryName, - }; + final Map data = new Map(); + data['_id'] = this.sId; + data['categoryName'] = this.categoryName; + return data; } } class GST { - final String id; - final String name; - final int tax; + String? sId; + String? name; + int? tax; - GST({ - required this.id, - required this.name, - required this.tax, - }); + GST({this.sId, this.name, this.tax}); - factory GST.fromJson(Map json) { - return GST( - id: json['_id'], - name: json['name'], - tax: json['tax'], - ); + GST.fromJson(Map json) { + sId = json['_id']; + name = json['name']; + tax = json['tax']; } Map toJson() { - return { - '_id': id, - 'name': name, - 'tax': tax, - }; + final Map data = new Map(); + data['_id'] = this.sId; + data['name'] = this.name; + data['tax'] = this.tax; + return data; } } class AddedBy { - final String id; - final String name; + String? sId; + String? name; - AddedBy({ - required this.id, - required this.name, - }); + AddedBy({this.sId, this.name}); - factory AddedBy.fromJson(Map json) { - return AddedBy( - id: json['_id'], - name: json['name'], - ); + AddedBy.fromJson(Map json) { + sId = json['_id']; + name = json['name']; } Map toJson() { - return { - '_id': id, - 'name': name, - }; + final Map data = new Map(); + data['_id'] = this.sId; + data['name'] = this.name; + return data; } -} \ No newline at end of file +} diff --git a/lib/provider/collect_kyc_provider.dart b/lib/provider/collect_kyc_provider.dart index bcf2bdd..a6cbfea 100644 --- a/lib/provider/collect_kyc_provider.dart +++ b/lib/provider/collect_kyc_provider.dart @@ -262,7 +262,7 @@ class CollectKycProvider extends ChangeNotifier { Navigator.push( context, MaterialPageRoute( - builder: (context) => const DataSubmitSuccessfull())); + builder: (context) => const DataSubmitSuccessFullScreen())); } } else { if (context.mounted) { diff --git a/lib/provider/pd_rd_provider.dart b/lib/provider/pd_rd_provider.dart new file mode 100644 index 0000000..1b00e65 --- /dev/null +++ b/lib/provider/pd_rd_provider.dart @@ -0,0 +1,73 @@ +import 'package:cheminova/constants/constant.dart'; +import 'package:cheminova/models/get_pd_rd_response.dart'; +import 'package:cheminova/screens/Add_products_screen.dart'; +import 'package:cheminova/services/api_client.dart'; +import 'package:cheminova/services/api_urls.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; + +class PdRdProvider extends ChangeNotifier { + String? selectedDistributorType; + + bool _isLoading = false; + + bool get isLoading => _isLoading; + + List _pdRdList = []; + + List get pdRdList => _pdRdList; + + GetPdRdResponse? selectedPdRd; + + final _apiClient = ApiClient(); + + void setLoading(bool loading) { + _isLoading = loading; + notifyListeners(); + } + + Future getPdRd() async { + setLoading(true); + try { + Response response = await _apiClient.get( + ApiUrls.getPdRdUrl + selectedDistributorType!.replaceAll(' ', '')); + setLoading(false); + if (response.statusCode == 200) { + _pdRdList = (response.data as List) + .map((json) => GetPdRdResponse.fromJson(json)) + .toList(); + notifyListeners(); + } else { + debugPrint("Failed to load data: ${response.statusCode}"); + } + } catch (e) { + debugPrint("Error occurred: $e"); + } finally { + setLoading(false); + } + } + + void updateDistributorType(String? val) { + selectedDistributorType = val; + selectedPdRd = null; + notifyListeners(); + getPdRd(); + } + + updatePdRdValue(GetPdRdResponse? val) { + selectedPdRd = val; + notifyListeners(); + + Future.delayed(const Duration(milliseconds: 500), () { + if (selectedPdRd != null && selectedDistributorType != null) { + Navigator.push( + navigatorKey.currentContext!, + MaterialPageRoute( + builder: (context) => AddProductsScreen( + distributorType: selectedDistributorType!, + tradeName: selectedPdRd!.name ?? '', + pdRdId: selectedPdRd!.sId!))); + } + }); + } +} diff --git a/lib/provider/products_provider.dart b/lib/provider/products_provider.dart index cab7127..83b3233 100644 --- a/lib/provider/products_provider.dart +++ b/lib/provider/products_provider.dart @@ -1,41 +1,125 @@ +import 'dart:convert'; + +import 'package:cheminova/constants/constant.dart'; +import 'package:cheminova/screens/data_submit_successfull.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'; +import 'package:flutter/material.dart'; import '../models/products_response.dart'; class ProductProvider extends ChangeNotifier { - ProductProvider() { - getProducts(); - } - final _apiClient = ApiClient(); ProductResponse? productResponse; - List productList = []; + List productList = []; + List searchList = []; bool _isLoading = false; bool get isLoading => _isLoading; + List selectedProducts = []; + void setLoading(bool loading) { _isLoading = loading; notifyListeners(); } + void filterProducts(String query) { + searchList = productList.where((product) { + final productNameLower = product.productName.toLowerCase(); + final productSkuLower = product.sku.toLowerCase(); + final searchLower = query.toLowerCase(); + return productNameLower.contains(searchLower) || + productSkuLower.contains(searchLower); + }).toList(); + notifyListeners(); + } + Future getProducts() async { - setLoading(true); - try { + // setLoading(true); + // try { Response response = await _apiClient.get(ApiUrls.getProducts); + debugPrint('Response: $response'); setLoading(false); if (response.statusCode == 200) { productResponse = ProductResponse.fromJson(response.data); - productList = productResponse!.product; + productList = productResponse!.products! + .map((product) => + ProductModel(sku: product.sKU!, productName: product.name!)) + .toList(); notifyListeners(); } + // } catch (e) { + // setLoading(false); + // debugPrint("Error: $e"); + // } + } + + Future submitProducts( + {required String distributorType, required String pdRdId}) async { + setLoading(true); + try { + Response response = await _apiClient.post(ApiUrls.submitProductUrl, + data: json.encode({ + "addedFor": distributorType.replaceAll(' ', ''), + "addedForId": pdRdId, + "products": selectedProducts.map((e) => e.toJson()).toList() + })); + setLoading(false); + if (response.statusCode == 201) { + ScaffoldMessenger.of( + navigatorKey.currentContext!, + ).showSnackBar( + SnackBar(content: Text(response.data['message'])), + ); + resetProducts(); + Navigator.push( + navigatorKey.currentContext!, + MaterialPageRoute( + builder: (context) => const DataSubmitSuccessFullScreen())); + } } catch (e) { setLoading(false); - print("Error: $e"); + debugPrint("Error: $e"); } } -} \ No newline at end of file + + void resetProducts() { + selectedProducts.clear(); + productList.clear(); + productResponse = null; + notifyListeners(); + } +} + +class ProductModel { + String sku; + String productName; + int? sale; + int? inventory; + + ProductModel( + {required this.sku, + required this.productName, + this.sale, + this.inventory}); + + factory ProductModel.fromJson(Map json) { + return ProductModel( + sku: json['SKU'], + productName: json['ProductName'], + sale: json['Sale'], + inventory: json['Inventory']); + } + + Map toJson() { + return { + 'SKU': sku, + 'ProductName': productName, + 'Sale': sale, + 'Inventory': inventory + }; + } +} diff --git a/lib/screens/Add_products_screen.dart b/lib/screens/Add_products_screen.dart index 45f46c3..1f5341a 100644 --- a/lib/screens/Add_products_screen.dart +++ b/lib/screens/Add_products_screen.dart @@ -1,235 +1,261 @@ -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:flutter/services.dart'; import 'package:provider/provider.dart'; -import '../models/products_response.dart'; + import '../provider/products_provider.dart'; class AddProductsScreen extends StatefulWidget { - const AddProductsScreen({super.key}); + final String distributorType; + final String tradeName; + final String pdRdId; + + const AddProductsScreen({ + super.key, + required this.distributorType, + required this.tradeName, + required this.pdRdId + }); @override State createState() => _AddProductsScreenState(); } class _AddProductsScreenState extends State { - List selectedProducts = []; - List filteredProducts = []; final searchController = TextEditingController(); - late ProductProvider provider; + late ProductProvider productProvider; @override void initState() { + productProvider = Provider.of(context, listen: false); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + productProvider.getProducts(); + }); super.initState(); - provider=Provider.of(context,listen: false); - provider.getProducts(); - filteredProducts = provider.productList; } - 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 + void dispose() { + if (mounted) { + productProvider.resetProducts(); + } + super.dispose(); } @override Widget build(BuildContext context) { - - return - CommonBackground( + 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()); - } - - return Stack( + appBar: CommonAppBar( + actions: [ + IconButton( + onPressed: () => Navigator.pop(context), + icon: Image.asset('assets/Back_attendance.png'), + padding: const EdgeInsets.only(right: 20) + ) + ], + title: Text( + '${widget.distributorType}\n${widget.tradeName}', + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 20, + color: Colors.black, + fontWeight: FontWeight.w400, + fontFamily: 'Anek' + ) + ), + backgroundColor: Colors.transparent, + elevation: 0 + ), + drawer: const CommonDrawer(), + bottomNavigationBar: Consumer( + builder: (context, value, child) => Column( + mainAxisSize: MainAxisSize.min, 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( - isScrollControlled: true, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height * 0.9, + alignment: value.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: (val) { + value.filterProducts(val); + setState(() {}); + } + ) + ), + Expanded( + child: ListView.builder( + itemCount: searchController.text.isEmpty + ? value.productList.length + : value.searchList.length, + itemBuilder: (context, index) { + bool isAlreadySelected = value.selectedProducts.any( + (selectedProduct) => selectedProduct.sku == value.productList[index].sku + ); + final data = searchController.text.isEmpty + ? value.productList[index] + : value.searchList[index]; + return Card( + child: ListTile( + title: Text( + data.productName, + style: TextStyle(color: isAlreadySelected ? Colors.grey : Colors.black) + ), + subtitle: Text( + data.sku, + style: TextStyle(color: isAlreadySelected ? Colors.grey : Colors.black) + ), + onTap: isAlreadySelected + ? null + : () { + setState(() => value.selectedProducts.add(data)); + 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)) ), - 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), + if (value.selectedProducts.isNotEmpty) ...[ + const SizedBox(height: 16.0), + Consumer( + builder: (context, value, child) => CommonElevatedButton( + borderRadius: 30, + width: double.infinity, + height: kToolbarHeight - 10, + text: 'SUBMIT', + backgroundColor: const Color(0xff004791), + onPressed: () { + if (value.selectedProducts.isNotEmpty && + value.selectedProducts.every((product) => + product.sku.isNotEmpty && + product.productName.isNotEmpty && + product.sale != null && + product.inventory != null)) { + value.submitProducts( + distributorType: widget.distributorType, + pdRdId: widget.pdRdId + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Please fill out all product details, including sale and inventory.') ), - 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(), - ), - ); - }, - ), - ], - ], - ), - ), + ); + } + } + ) + ) + ] + ] + ) + ) ), ], - ); - }, - ), - ), - ); - + ), + ), + body: Consumer( + builder: (context, value, child) { + return Stack( + children: [ + Column( + children: [ + if (value.selectedProducts.isNotEmpty) + Expanded( + child: ListView.builder( + itemCount: value.selectedProducts.length, + itemBuilder: (context, index) { + return ProductBlock( + onUpdate: (updatedProduct) { + debugPrint('selected productt le ${value.selectedProducts.length}'); + setState(() { + value.selectedProducts[index] = updatedProduct; + }); + }, + onRemove: () { + setState(() { + value.selectedProducts.removeAt(index); + }); + }, + product: value.selectedProducts[index] + ); + } + ) + ) + ] + ), + (value.isLoading) + ? Container( + color: Colors.black12, + child: const Center(child: CircularProgressIndicator()) + ) + : const SizedBox() + ] + ); + } + ) + ) + ); } } class ProductBlock extends StatefulWidget { - final Product product; + final ProductModel product; + final ValueChanged onUpdate; + final VoidCallback onRemove; - const ProductBlock({super.key, required this.product}); + const ProductBlock({ + super.key, + required this.product, + required this.onUpdate, + required this.onRemove, + }); @override - _ProductBlockState createState() => _ProductBlockState(); + State createState() => _ProductBlockState(); } class _ProductBlockState extends State { @@ -240,21 +266,36 @@ 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) { + String? saleError; + String? inventoryError; + + if (saleController.text.isEmpty) { + saleError = 'Sale cannot be empty.'; + } + + if (inventoryController.text.isEmpty) { + inventoryError = 'Inventory cannot be empty.'; + } + + errorMessage = null; + if (saleError == null && inventoryError == null) { 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; - } + + widget.onUpdate(ProductModel( + sku: widget.product.sku, + productName: widget.product.productName, + sale: sale, + inventory: inventory, + )); } else { - errorMessage = null; + errorMessage = saleError ?? inventoryError; } }); } @@ -262,46 +303,66 @@ class _ProductBlockState extends State { @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, + color: Colors.white, + margin: const EdgeInsets.all(8), + child: Stack( 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), - ), + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Product: ${widget.product.productName}', + style: const TextStyle(fontSize: 16)), + Text('SKU: ${widget.product.sku}', + style: const TextStyle(fontSize: 15)), + const SizedBox(height: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + controller: saleController, + onTapOutside: (event) => FocusScope.of(context).unfocus(), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: InputDecoration( + labelText: 'Sale', + errorText: saleController.text.isEmpty + ? 'Sale cannot be empty.' + : null + ), + keyboardType: TextInputType.number, + enabled: true, + onChanged: (_) => validateInput() + ), + TextField( + controller: inventoryController, + onTapOutside: (event) => FocusScope.of(context).unfocus(), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: InputDecoration( + labelText: 'Inventory', + errorText: inventoryController.text.isEmpty + ? 'Inventory cannot be empty.' + : null + ), + keyboardType: TextInputType.number, + enabled: true, + onChanged: (_) => validateInput() + ) + ] + ) + ] ), + ), + Positioned( + top: 4, + right: 4, + child: IconButton( + icon: const Icon(Icons.delete_outline, color: Colors.red), + onPressed: widget.onRemove, + ), + ), ], - ), - ), + ) ); } } \ No newline at end of file diff --git a/lib/screens/data_submit_successfull.dart b/lib/screens/data_submit_successfull.dart index 4e7dc44..32dffb0 100644 --- a/lib/screens/data_submit_successfull.dart +++ b/lib/screens/data_submit_successfull.dart @@ -2,14 +2,14 @@ import 'package:cheminova/screens/home_screen.dart'; import 'package:cheminova/widgets/common_background.dart'; import 'package:flutter/material.dart'; -class DataSubmitSuccessfull extends StatefulWidget { - const DataSubmitSuccessfull({super.key}); +class DataSubmitSuccessFullScreen extends StatefulWidget { + const DataSubmitSuccessFullScreen({super.key}); @override - State createState() => DataSubmitSuccessfullState(); + State createState() => DataSubmitSuccessFullScreenState(); } -class DataSubmitSuccessfullState extends State { +class DataSubmitSuccessFullScreenState extends State { @override void initState() { super.initState(); diff --git a/lib/screens/display_sales_screen.dart b/lib/screens/display_sales_screen.dart index 5fc3671..a41cb7e 100644 --- a/lib/screens/display_sales_screen.dart +++ b/lib/screens/display_sales_screen.dart @@ -118,7 +118,7 @@ class DisplaySalesScreenState extends State { text: 'SUBMIT', backgroundColor: const Color(0xff004791), onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessfull())); + Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessFullScreen())); }) diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 039a57a..bd21fc2 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -30,14 +30,15 @@ class _HomePageState extends State { @override void initState() { - Provider.of(context, listen: false).getProfile(); super.initState(); - notificationServices.requestNotificationPermission(); - - notificationServices. getDeviceToken().then((value) { - if (kDebugMode) { - print('Device Token: $value'); - } + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + Provider.of(context, listen: false).getProfile(); + notificationServices.requestNotificationPermission(); + notificationServices.getDeviceToken().then((value) { + if (kDebugMode) { + print('Device Token: $value'); + } + }); }); } diff --git a/lib/screens/product_sales_data.dart b/lib/screens/product_sales_data.dart index 3945af0..d70cb8b 100644 --- a/lib/screens/product_sales_data.dart +++ b/lib/screens/product_sales_data.dart @@ -99,9 +99,7 @@ class ProductSalesDataState extends State { height: kToolbarHeight - 10, text: 'VIEW DATA', backgroundColor: const Color(0xff004791), - onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessfull(),)); - }, + onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessFullScreen(),)), ) diff --git a/lib/screens/summary_screen.dart b/lib/screens/summary_screen.dart index 352a236..b1489b8 100644 --- a/lib/screens/summary_screen.dart +++ b/lib/screens/summary_screen.dart @@ -108,7 +108,7 @@ class SummaryScreenState extends State { text: 'VIEW DATA', backgroundColor: const Color(0xff004791), onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessfull())); + Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessFullScreen())); }) ), diff --git a/lib/screens/update_inventory_screen.dart b/lib/screens/update_inventory_screen.dart index 62b2be9..0aacede 100644 --- a/lib/screens/update_inventory_screen.dart +++ b/lib/screens/update_inventory_screen.dart @@ -1,10 +1,10 @@ -import 'package:cheminova/screens/Add_products_screen.dart'; -import 'package:flutter/material.dart'; -import 'package:cheminova/widgets/common_background.dart'; +import 'package:cheminova/models/get_pd_rd_response.dart'; +import 'package:cheminova/provider/pd_rd_provider.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'; class UpdateInventoryScreen extends StatefulWidget { const UpdateInventoryScreen({super.key}); @@ -14,160 +14,89 @@ class UpdateInventoryScreen extends StatefulWidget { } class _UpdateInventoryScreenState 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 List principalDistributors = ['vaibhav', 'sonu', 'monu']; - final List retailerDistributors = ['shivam', 'vivek']; - String? selectedDistributorType; - String? selectedDistributor; - final searchController = TextEditingController(); + late PdRdProvider pdRdProvider; @override void initState() { super.initState(); - filteredProducts = products; - } - - void filterProducts(String query) { - setState(() { - filteredProducts = products.where((product) { - final productNameLower = product.name.toLowerCase(); - final productSkuLower = product.sku.toLowerCase(); - final searchLower = query.toLowerCase(); - - return productNameLower.contains(searchLower) || - productSkuLower.contains(searchLower); - }).toList(); - }); + pdRdProvider = PdRdProvider(); } @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('Update Inventory Data', - style: TextStyle( - fontSize: 20, - color: Colors.black, - fontWeight: FontWeight.w400, - fontFamily: 'Anek')), - backgroundColor: Colors.transparent, - elevation: 0, - ), - drawer: const CommonDrawer(), - 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(), - ), - ); - }, - ), - ), - 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(), - ), - 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'), - ), - ), - // Show the selected products - if (selectedProducts.isNotEmpty) - Expanded( - child: ListView.builder( - itemCount: selectedProducts.length, - itemBuilder: (context, index) { - return ProductBlock(product: selectedProducts[index]); - }, - ), - ), - ], - ), - ], - ), - ), - ); + return ChangeNotifierProvider( + create: (context) => pdRdProvider, + child: 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('Update Inventory Data', + style: TextStyle( + fontSize: 20, + color: Colors.black, + fontWeight: FontWeight.w400, + fontFamily: 'Anek')), + backgroundColor: Colors.transparent, + elevation: 0), + drawer: const CommonDrawer(), + body: Stack(children: [ + Consumer( + builder: (context, value, child) => Column(children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15.0, vertical: 25), + child: DropdownButtonFormField( + decoration: const InputDecoration( + fillColor: Colors.white, + filled: true, + border: OutlineInputBorder()), + value: value.selectedDistributorType, + items: [ + 'Principal Distributor', + 'Retail Distributor' + ].map((String type) { + return DropdownMenuItem( + value: type, child: Text(type)); + }).toList(), + hint: const Text('Select Distributor Type'), + onChanged: (val) => + value.updateDistributorType(val))), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15.0, vertical: 25), + child: DropdownButtonFormField( + decoration: const InputDecoration( + fillColor: Colors.white, + filled: true, + border: OutlineInputBorder()), + value: value.selectedPdRd, + items: value.pdRdList.map((e) { + return DropdownMenuItem( + value: e, child: Text(e.name ?? '')); + }).toList(), + onChanged: (val) => + value.updatePdRdValue(val), + // Disable the dropdown if no distributor type is selected + isExpanded: true, + isDense: true, + iconSize: 24, + hint: + const Text('Select Distributor Name'))) + ])), + Consumer( + builder: (context, value, child) => value.isLoading + ? Container( + color: Colors.black12, + child: const Center( + child: CircularProgressIndicator())) + : const SizedBox()) + ])))); } } @@ -210,7 +139,8 @@ class _ProductBlockState extends State { 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) { @@ -234,8 +164,10 @@ class _ProductBlockState extends State { 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, diff --git a/lib/services/api_client.dart b/lib/services/api_client.dart index 22a856c..d53034d 100644 --- a/lib/services/api_client.dart +++ b/lib/services/api_client.dart @@ -1,6 +1,7 @@ import 'package:cheminova/services/secure__storage_service.dart'; import 'package:dio/dio.dart'; import 'package:cheminova/services/api_urls.dart'; +import 'package:flutter/material.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; class ApiClient { @@ -19,6 +20,7 @@ class ApiClient { .add(InterceptorsWrapper(onRequest: (options, handler) async { String? token = await _storageService.read(key: 'access_token'); if (token != null) { + debugPrint('Token start ------------> $token <------------ Token end'); options.headers['Authorization'] = 'Bearer $token'; } return handler.next(options); diff --git a/lib/services/api_urls.dart b/lib/services/api_urls.dart index 6758bb0..47fb01d 100644 --- a/lib/services/api_urls.dart +++ b/lib/services/api_urls.dart @@ -14,5 +14,6 @@ class ApiUrls { 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'; - + static const String getPdRdUrl = '${baseUrl}inventory/distributors-SC/'; + static const String submitProductUrl = '${baseUrl}inventory/add-SC'; }