API of products and PD/RD & Validations

This commit is contained in:
Vaibhav 2024-08-16 17:54:32 +05:30
parent 600bcdd3d7
commit d734bbb6ee
16 changed files with 720 additions and 559 deletions

View File

@ -8,3 +8,6 @@ final GlobalKey<ScaffoldMessengerState> _scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState> get scaffoldMessengerKey => GlobalKey<ScaffoldMessengerState> get scaffoldMessengerKey =>
_scaffoldMessengerKey; _scaffoldMessengerKey;
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

View File

@ -1,5 +1,6 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:cheminova/constants/constant.dart';
import 'package:cheminova/provider/home_provider.dart'; import 'package:cheminova/provider/home_provider.dart';
import 'package:cheminova/provider/products_provider.dart'; import 'package:cheminova/provider/products_provider.dart';
import 'package:cheminova/screens/splash_screen.dart'; import 'package:cheminova/screens/splash_screen.dart';
@ -106,7 +107,7 @@ class _MyAppState extends State<MyApp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
// scaffoldMessengerKey: SnackBarService().scaffoldMessengerKey, navigatorKey: navigatorKey,
title: 'cheminova', title: 'cheminova',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),

View File

@ -0,0 +1,18 @@
class GetPdRdResponse {
String? sId;
String? name;
GetPdRdResponse({this.sId, this.name});
GetPdRdResponse.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
name = json['name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['_id'] = sId;
data['name'] = name;
return data;
}
}

View File

@ -1,181 +1,168 @@
class ProductResponse { class ProductResponse {
final bool success; bool? success;
final int totalData; int? totalData;
final int totalPages; int? totalPages;
final List<Product> product; List<Products>? products;
ProductResponse({ ProductResponse(
required this.success, {this.success, this.totalData, this.totalPages, this.products});
required this.totalData,
required this.totalPages,
required this.product,
});
factory ProductResponse.fromJson(Map<String, dynamic> json) { ProductResponse.fromJson(Map<String, dynamic> json) {
return ProductResponse( success = json['success'];
success: json['success'], totalData = json['total_data'];
totalData: json['total_data'], totalPages = json['total_pages'];
totalPages: json['total_pages'], if (json['products'] != null) {
product: (json['product'] as List) products = <Products>[];
.map((item) => Product.fromJson(item)) json['products'].forEach((v) {
.toList(), products!.add(new Products.fromJson(v));
); });
}
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { final Map<String, dynamic> data = new Map<String, dynamic>();
'success': success, data['success'] = this.success;
'total_data': totalData, data['total_data'] = this.totalData;
'total_pages': totalPages, data['total_pages'] = this.totalPages;
'product': product.map((item) => item.toJson()).toList(), if (this.products != null) {
}; data['products'] = this.products!.map((v) => v.toJson()).toList();
}
return data;
} }
} }
class Product { class Products {
final String id; String? sId;
final String SKU; String? sKU;
final String name; String? name;
final Category category; Category? category;
final double price; int? price;
final GST gst; GST? gST;
final String description; String? description;
final String specialInstructions; String? specialInstructions;
final String productStatus; String? productStatus;
final AddedBy addedBy; AddedBy? addedBy;
final List<dynamic> image; List<Null>? image;
final DateTime createdAt; String? createdAt;
final DateTime updatedAt; String? updatedAt;
final int v; int? iV;
Product({ Products(
required this.id, {this.sId,
required this.SKU, this.sKU,
required this.name, this.name,
required this.category, this.category,
required this.price, this.price,
required this.gst, this.gST,
required this.description, this.description,
required this.specialInstructions, this.specialInstructions,
required this.productStatus, this.productStatus,
required this.addedBy, this.addedBy,
required this.image, this.image,
required this.createdAt, this.createdAt,
required this.updatedAt, this.updatedAt,
required this.v, this.iV});
});
factory Product.fromJson(Map<String, dynamic> json) { Products.fromJson(Map<String, dynamic> json) {
return Product( sId = json['_id'];
id: json['_id'], sKU = json['SKU'];
SKU: json['SKU'], name = json['name'];
name: json['name'], category = json['category'] != null
category: Category.fromJson(json['category']), ? new Category.fromJson(json['category'])
price: (json['price'] as num).toDouble(), : null;
gst: GST.fromJson(json['GST']), price = json['price'];
description: json['description'], gST = json['GST'] != null ? new GST.fromJson(json['GST']) : null;
specialInstructions: json['special_instructions'], description = json['description'];
productStatus: json['product_Status'], specialInstructions = json['special_instructions'];
addedBy: AddedBy.fromJson(json['addedBy']), productStatus = json['product_Status'];
image: json['image'] as List<dynamic>, addedBy =
createdAt: DateTime.parse(json['createdAt']), json['addedBy'] != null ? new AddedBy.fromJson(json['addedBy']) : null;
updatedAt: DateTime.parse(json['updatedAt']), createdAt = json['createdAt'];
v: json['__v'], updatedAt = json['updatedAt'];
); iV = json['__v'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { final Map<String, dynamic> data = new Map<String, dynamic>();
'_id': id, data['_id'] = this.sId;
'SKU': SKU, data['SKU'] = this.sKU;
'name': name, data['name'] = this.name;
'category': category.toJson(), if (this.category != null) {
'price': price, data['category'] = this.category!.toJson();
'GST': gst.toJson(), }
'description': description, data['price'] = this.price;
'special_instructions': specialInstructions, if (this.gST != null) {
'product_Status': productStatus, data['GST'] = this.gST!.toJson();
'addedBy': addedBy.toJson(), }
'image': image, data['description'] = this.description;
'createdAt': createdAt.toIso8601String(), data['special_instructions'] = this.specialInstructions;
'updatedAt': updatedAt.toIso8601String(), data['product_Status'] = this.productStatus;
'__v': v, 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 { class Category {
final String id; String? sId;
final String categoryName; String? categoryName;
Category({ Category({this.sId, this.categoryName});
required this.id,
required this.categoryName,
});
factory Category.fromJson(Map<String, dynamic> json) { Category.fromJson(Map<String, dynamic> json) {
return Category( sId = json['_id'];
id: json['_id'], categoryName = json['categoryName'];
categoryName: json['categoryName'],
);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { final Map<String, dynamic> data = new Map<String, dynamic>();
'_id': id, data['_id'] = this.sId;
'categoryName': categoryName, data['categoryName'] = this.categoryName;
}; return data;
} }
} }
class GST { class GST {
final String id; String? sId;
final String name; String? name;
final int tax; int? tax;
GST({ GST({this.sId, this.name, this.tax});
required this.id,
required this.name,
required this.tax,
});
factory GST.fromJson(Map<String, dynamic> json) { GST.fromJson(Map<String, dynamic> json) {
return GST( sId = json['_id'];
id: json['_id'], name = json['name'];
name: json['name'], tax = json['tax'];
tax: json['tax'],
);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { final Map<String, dynamic> data = new Map<String, dynamic>();
'_id': id, data['_id'] = this.sId;
'name': name, data['name'] = this.name;
'tax': tax, data['tax'] = this.tax;
}; return data;
} }
} }
class AddedBy { class AddedBy {
final String id; String? sId;
final String name; String? name;
AddedBy({ AddedBy({this.sId, this.name});
required this.id,
required this.name,
});
factory AddedBy.fromJson(Map<String, dynamic> json) { AddedBy.fromJson(Map<String, dynamic> json) {
return AddedBy( sId = json['_id'];
id: json['_id'], name = json['name'];
name: json['name'],
);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { final Map<String, dynamic> data = new Map<String, dynamic>();
'_id': id, data['_id'] = this.sId;
'name': name, data['name'] = this.name;
}; return data;
} }
} }

View File

@ -262,7 +262,7 @@ class CollectKycProvider extends ChangeNotifier {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const DataSubmitSuccessfull())); builder: (context) => const DataSubmitSuccessFullScreen()));
} }
} else { } else {
if (context.mounted) { if (context.mounted) {

View File

@ -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<GetPdRdResponse> _pdRdList = [];
List<GetPdRdResponse> get pdRdList => _pdRdList;
GetPdRdResponse? selectedPdRd;
final _apiClient = ApiClient();
void setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
Future<void> 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!)));
}
});
}
}

View File

@ -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_client.dart';
import 'package:cheminova/services/api_urls.dart'; import 'package:cheminova/services/api_urls.dart';
import 'package:flutter/cupertino.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../models/products_response.dart'; import '../models/products_response.dart';
class ProductProvider extends ChangeNotifier { class ProductProvider extends ChangeNotifier {
ProductProvider() {
getProducts();
}
final _apiClient = ApiClient(); final _apiClient = ApiClient();
ProductResponse? productResponse; ProductResponse? productResponse;
List<Product> productList = []; List<ProductModel> productList = [];
List<ProductModel> searchList = [];
bool _isLoading = false; bool _isLoading = false;
bool get isLoading => _isLoading; bool get isLoading => _isLoading;
List<ProductModel> selectedProducts = [];
void setLoading(bool loading) { void setLoading(bool loading) {
_isLoading = loading; _isLoading = loading;
notifyListeners(); 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<void> getProducts() async { Future<void> getProducts() async {
setLoading(true); // setLoading(true);
try { // try {
Response response = await _apiClient.get(ApiUrls.getProducts); Response response = await _apiClient.get(ApiUrls.getProducts);
debugPrint('Response: $response');
setLoading(false); setLoading(false);
if (response.statusCode == 200) { if (response.statusCode == 200) {
productResponse = ProductResponse.fromJson(response.data); productResponse = ProductResponse.fromJson(response.data);
productList = productResponse!.product; productList = productResponse!.products!
.map((product) =>
ProductModel(sku: product.sKU!, productName: product.name!))
.toList();
notifyListeners(); notifyListeners();
} }
// } catch (e) {
// setLoading(false);
// debugPrint("Error: $e");
// }
}
Future<void> 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) { } catch (e) {
setLoading(false); setLoading(false);
print("Error: $e"); debugPrint("Error: $e");
} }
} }
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<String, dynamic> json) {
return ProductModel(
sku: json['SKU'],
productName: json['ProductName'],
sale: json['Sale'],
inventory: json['Inventory']);
}
Map<String, dynamic> toJson() {
return {
'SKU': sku,
'ProductName': productName,
'Sale': sale,
'Inventory': inventory
};
}
} }

View File

@ -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_app_bar.dart';
import 'package:cheminova/widgets/common_background.dart'; import 'package:cheminova/widgets/common_background.dart';
import 'package:cheminova/widgets/common_drawer.dart'; import 'package:cheminova/widgets/common_drawer.dart';
import 'package:cheminova/widgets/common_elevated_button.dart'; import 'package:cheminova/widgets/common_elevated_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../models/products_response.dart';
import '../provider/products_provider.dart'; import '../provider/products_provider.dart';
class AddProductsScreen extends StatefulWidget { 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 @override
State<AddProductsScreen> createState() => _AddProductsScreenState(); State<AddProductsScreen> createState() => _AddProductsScreenState();
} }
class _AddProductsScreenState extends State<AddProductsScreen> { class _AddProductsScreenState extends State<AddProductsScreen> {
List<Product> selectedProducts = [];
List<Product> filteredProducts = [];
final searchController = TextEditingController(); final searchController = TextEditingController();
late ProductProvider provider; late ProductProvider productProvider;
@override @override
void initState() { void initState() {
productProvider = Provider.of<ProductProvider>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
productProvider.getProducts();
});
super.initState(); super.initState();
provider=Provider.of<ProductProvider>(context,listen: false);
provider.getProducts();
filteredProducts = provider.productList;
} }
void filterProducts(String query) { @override
setState(() { void dispose() {
final provider = Provider.of<ProductProvider>(context, listen: false); if (mounted) {
filteredProducts = provider.productList.where((product) { productProvider.resetProducts();
final productNameLower = product.name.toLowerCase(); }
final productSkuLower = product.SKU.toLowerCase(); super.dispose();
final searchLower = query.toLowerCase();
return productNameLower.contains(searchLower) ||
productSkuLower.contains(searchLower);
}).toList();
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonBackground(
return
CommonBackground(
child: Scaffold( 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, backgroundColor: Colors.transparent,
elevation: 0, appBar: CommonAppBar(
), actions: [
drawer: const CommonDrawer(), IconButton(
body: Consumer<ProductProvider>( onPressed: () => Navigator.pop(context),
builder: (context, provider, child) { icon: Image.asset('assets/Back_attendance.png'),
if (provider.isLoading) { padding: const EdgeInsets.only(right: 20)
return const Center(child: CircularProgressIndicator()); )
} ],
title: Text(
return Stack( '${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<ProductProvider>(
builder: (context, value, child) => Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
Column(
children: [
if (selectedProducts.isNotEmpty)
Expanded(
child: ListView.builder(
itemCount: selectedProducts.length,
itemBuilder: (context, index) {
return ProductBlock(
product: selectedProducts[index]);
},
),
),
],
),
Align( Align(
alignment: selectedProducts.isEmpty alignment: value.selectedProducts.isEmpty
? Alignment.center ? Alignment.center
: Alignment.bottomCenter, : Alignment.bottomCenter,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
FloatingActionButton.extended( FloatingActionButton.extended(
onPressed: () { onPressed: () {
showModalBottomSheet( showModalBottomSheet(
isScrollControlled: true, isScrollControlled: true,
constraints: BoxConstraints( constraints: BoxConstraints(
maxHeight: maxHeight: MediaQuery.of(context).size.height * 0.9
MediaQuery.of(context).size.height * 0.9, ),
context: context,
builder: (BuildContext context) {
return Consumer<ProductProvider>(
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, if (value.selectedProducts.isNotEmpty) ...[
builder: (BuildContext context) { const SizedBox(height: 16.0),
return Consumer<ProductProvider>(builder: (context, value, child) =>StatefulBuilder( Consumer<ProductProvider>(
builder: (context, setState) { builder: (context, value, child) => CommonElevatedButton(
return Column( borderRadius: 30,
children: [ width: double.infinity,
Padding( height: kToolbarHeight - 10,
padding: const EdgeInsets.all(18.0), text: 'SUBMIT',
child: TextField( backgroundColor: const Color(0xff004791),
controller: searchController, onPressed: () {
decoration: const InputDecoration( if (value.selectedProducts.isNotEmpty &&
labelText: value.selectedProducts.every((product) =>
'Search by name or SKU', product.sku.isNotEmpty &&
border: OutlineInputBorder(), product.productName.isNotEmpty &&
prefixIcon: Icon(Icons.search), 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<ProductProvider>(
), 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 { class ProductBlock extends StatefulWidget {
final Product product; final ProductModel product;
final ValueChanged<ProductModel> 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 @override
_ProductBlockState createState() => _ProductBlockState(); State<ProductBlock> createState() => _ProductBlockState();
} }
class _ProductBlockState extends State<ProductBlock> { class _ProductBlockState extends State<ProductBlock> {
@ -240,21 +266,36 @@ class _ProductBlockState extends State<ProductBlock> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
saleController.text = (widget.product.sale ?? '').toString();
inventoryController.text = (widget.product.inventory ?? '').toString();
} }
void validateInput() { void validateInput() {
setState(() { setState(() {
if (saleController.text.isNotEmpty && String? saleError;
inventoryController.text.isNotEmpty) { 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 sale = int.parse(saleController.text);
int inventory = int.parse(inventoryController.text); int inventory = int.parse(inventoryController.text);
if (inventory > sale) {
errorMessage = 'Inventory should be less than or equal to sales'; widget.onUpdate(ProductModel(
} else { sku: widget.product.sku,
errorMessage = null; productName: widget.product.productName,
} sale: sale,
inventory: inventory,
));
} else { } else {
errorMessage = null; errorMessage = saleError ?? inventoryError;
} }
}); });
} }
@ -262,46 +303,66 @@ class _ProductBlockState extends State<ProductBlock> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Card(
// color: !widget.product.isPurchased ? Colors.white54 : Colors.white, color: Colors.white,
color: Colors.white, margin: const EdgeInsets.all(8),
margin: const EdgeInsets.all(8), child: Stack(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Product: ${widget.product.name}', Padding(
style: const TextStyle(fontSize: 16)), padding: const EdgeInsets.all(16),
Text('SKU: ${widget.product.SKU}', child: Column(
style: const TextStyle(fontSize: 15)), crossAxisAlignment: CrossAxisAlignment.start,
const SizedBox(height: 8), children: [
TextField( Text('Product: ${widget.product.productName}',
controller: saleController, style: const TextStyle(fontSize: 16)),
decoration: const InputDecoration(labelText: 'Sale'), Text('SKU: ${widget.product.sku}',
keyboardType: TextInputType.number, style: const TextStyle(fontSize: 15)),
// enabled: widget.product.isPurchased, const SizedBox(height: 8),
enabled: true, Column(
onChanged: (_) => validateInput(), crossAxisAlignment: CrossAxisAlignment.start,
), children: [
TextField( TextField(
controller: inventoryController, controller: saleController,
decoration: const InputDecoration(labelText: 'Inventory'), onTapOutside: (event) => FocusScope.of(context).unfocus(),
keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly],
// enabled: widget.product.isPurchased, decoration: InputDecoration(
enabled: true, labelText: 'Sale',
onChanged: (_) => validateInput(), errorText: saleController.text.isEmpty
), ? 'Sale cannot be empty.'
if (errorMessage != null) : null
Padding( ),
padding: const EdgeInsets.only(top: 8.0), keyboardType: TextInputType.number,
child: Text( enabled: true,
errorMessage!, onChanged: (_) => validateInput()
style: const TextStyle(color: Colors.red), ),
), 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,
),
),
], ],
), )
),
); );
} }
} }

View File

@ -2,14 +2,14 @@ import 'package:cheminova/screens/home_screen.dart';
import 'package:cheminova/widgets/common_background.dart'; import 'package:cheminova/widgets/common_background.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class DataSubmitSuccessfull extends StatefulWidget { class DataSubmitSuccessFullScreen extends StatefulWidget {
const DataSubmitSuccessfull({super.key}); const DataSubmitSuccessFullScreen({super.key});
@override @override
State<DataSubmitSuccessfull> createState() => DataSubmitSuccessfullState(); State<DataSubmitSuccessFullScreen> createState() => DataSubmitSuccessFullScreenState();
} }
class DataSubmitSuccessfullState extends State<DataSubmitSuccessfull> { class DataSubmitSuccessFullScreenState extends State<DataSubmitSuccessFullScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();

View File

@ -118,7 +118,7 @@ class DisplaySalesScreenState extends State<DisplaySalesScreen> {
text: 'SUBMIT', text: 'SUBMIT',
backgroundColor: const Color(0xff004791), backgroundColor: const Color(0xff004791),
onPressed: () { onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessfull())); Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessFullScreen()));
}) })

View File

@ -30,14 +30,15 @@ class _HomePageState extends State<HomePage> {
@override @override
void initState() { void initState() {
Provider.of<HomeProvider>(context, listen: false).getProfile();
super.initState(); super.initState();
notificationServices.requestNotificationPermission(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<HomeProvider>(context, listen: false).getProfile();
notificationServices. getDeviceToken().then((value) { notificationServices.requestNotificationPermission();
if (kDebugMode) { notificationServices.getDeviceToken().then((value) {
print('Device Token: $value'); if (kDebugMode) {
} print('Device Token: $value');
}
});
}); });
} }

View File

@ -99,9 +99,7 @@ class ProductSalesDataState extends State<ProductSalesData> {
height: kToolbarHeight - 10, height: kToolbarHeight - 10,
text: 'VIEW DATA', text: 'VIEW DATA',
backgroundColor: const Color(0xff004791), backgroundColor: const Color(0xff004791),
onPressed: () { onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessFullScreen(),)),
Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessfull(),));
},
) )

View File

@ -108,7 +108,7 @@ class SummaryScreenState extends State<SummaryScreen> {
text: 'VIEW DATA', text: 'VIEW DATA',
backgroundColor: const Color(0xff004791), backgroundColor: const Color(0xff004791),
onPressed: () { onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessfull())); Navigator.push(context, MaterialPageRoute(builder: (context) => const DataSubmitSuccessFullScreen()));
}) })
), ),

View File

@ -1,10 +1,10 @@
import 'package:cheminova/screens/Add_products_screen.dart'; import 'package:cheminova/models/get_pd_rd_response.dart';
import 'package:flutter/material.dart'; import 'package:cheminova/provider/pd_rd_provider.dart';
import 'package:cheminova/widgets/common_background.dart';
import 'package:cheminova/widgets/common_app_bar.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_drawer.dart';
import 'package:cheminova/widgets/common_elevated_button.dart'; import 'package:flutter/material.dart';
import 'package:cheminova/screens/data_submit_successfull.dart'; import 'package:provider/provider.dart';
class UpdateInventoryScreen extends StatefulWidget { class UpdateInventoryScreen extends StatefulWidget {
const UpdateInventoryScreen({super.key}); const UpdateInventoryScreen({super.key});
@ -14,160 +14,89 @@ class UpdateInventoryScreen extends StatefulWidget {
} }
class _UpdateInventoryScreenState extends State<UpdateInventoryScreen> { class _UpdateInventoryScreenState extends State<UpdateInventoryScreen> {
final List<Product> products = [ late PdRdProvider pdRdProvider;
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<Product> selectedProducts = [];
List<Product> filteredProducts = [];
final List<String> principalDistributors = ['vaibhav', 'sonu', 'monu'];
final List<String> retailerDistributors = ['shivam', 'vivek'];
String? selectedDistributorType;
String? selectedDistributor;
final searchController = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
filteredProducts = products; pdRdProvider = PdRdProvider();
}
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();
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CommonBackground( return ChangeNotifierProvider(
child: Scaffold( create: (context) => pdRdProvider,
backgroundColor: Colors.transparent, child: CommonBackground(
appBar: CommonAppBar( child: Scaffold(
actions: [ backgroundColor: Colors.transparent,
IconButton( appBar: CommonAppBar(
onPressed: () { actions: [
Navigator.pop(context); IconButton(
}, onPressed: () => Navigator.pop(context),
icon: Image.asset('assets/Back_attendance.png'), icon: Image.asset('assets/Back_attendance.png'),
padding: const EdgeInsets.only(right: 20), padding: const EdgeInsets.only(right: 20))
), ],
], title: const Text('Update Inventory Data',
title: const Text('Update Inventory Data', style: TextStyle(
style: TextStyle( fontSize: 20,
fontSize: 20, color: Colors.black,
color: Colors.black, fontWeight: FontWeight.w400,
fontWeight: FontWeight.w400, fontFamily: 'Anek')),
fontFamily: 'Anek')), backgroundColor: Colors.transparent,
backgroundColor: Colors.transparent, elevation: 0),
elevation: 0, drawer: const CommonDrawer(),
), body: Stack(children: [
drawer: const CommonDrawer(), Consumer<PdRdProvider>(
bottomNavigationBar: Padding( builder: (context, value, child) => Column(children: [
padding: const EdgeInsets.all(16.0), Padding(
child: CommonElevatedButton( padding: const EdgeInsets.symmetric(
borderRadius: 30, horizontal: 15.0, vertical: 25),
width: double.infinity, child: DropdownButtonFormField<String>(
height: kToolbarHeight - 10, decoration: const InputDecoration(
text: 'SUBMIT', fillColor: Colors.white,
backgroundColor: const Color(0xff004791), filled: true,
onPressed: () { border: OutlineInputBorder()),
Navigator.push( value: value.selectedDistributorType,
context, items: [
MaterialPageRoute( 'Principal Distributor',
builder: (context) => const AddProductsScreen(), 'Retail Distributor'
), ].map((String type) {
); return DropdownMenuItem<String>(
}, value: type, child: Text(type));
), }).toList(),
), hint: const Text('Select Distributor Type'),
body: Stack( onChanged: (val) =>
children: [ value.updateDistributorType(val))),
Column( Padding(
children: [ padding: const EdgeInsets.symmetric(
// Dropdown for selecting distributor type horizontal: 15.0, vertical: 25),
Padding( child: DropdownButtonFormField<GetPdRdResponse>(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 25), decoration: const InputDecoration(
child: DropdownButtonFormField<String>( fillColor: Colors.white,
decoration: const InputDecoration( filled: true,
labelText: 'Select Distributor Type', border: OutlineInputBorder()),
fillColor: Colors.white, value: value.selectedPdRd,
filled: true, items: value.pdRdList.map((e) {
border: OutlineInputBorder(), return DropdownMenuItem<GetPdRdResponse>(
), value: e, child: Text(e.name ?? ''));
value: selectedDistributorType, }).toList(),
items: ['Principal Distributor', 'Retailer Distributor'].map((String type) { onChanged: (val) =>
return DropdownMenuItem<String>( value.updatePdRdValue(val),
value: type, // Disable the dropdown if no distributor type is selected
child: Text(type), isExpanded: true,
); isDense: true,
}).toList(), iconSize: 24,
onChanged: (value) { hint:
setState(() { const Text('Select Distributor Name')))
selectedDistributorType = value; ])),
selectedDistributor = null; // Reset distributor selection when type changes Consumer<PdRdProvider>(
}); builder: (context, value, child) => value.isLoading
}, ? Container(
), color: Colors.black12,
), child: const Center(
// Dropdown for selecting distributor name based on type child: CircularProgressIndicator()))
Padding( : const SizedBox())
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 25), ]))));
child: DropdownButtonFormField<String>(
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<String>(
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]);
},
),
),
],
),
],
),
),
);
} }
} }
@ -210,7 +139,8 @@ class _ProductBlockState extends State<ProductBlock> {
void validateInput() { void validateInput() {
setState(() { setState(() {
if (saleController.text.isNotEmpty && inventoryController.text.isNotEmpty) { if (saleController.text.isNotEmpty &&
inventoryController.text.isNotEmpty) {
int sale = int.parse(saleController.text); int sale = int.parse(saleController.text);
int inventory = int.parse(inventoryController.text); int inventory = int.parse(inventoryController.text);
if (inventory > sale) { if (inventory > sale) {
@ -234,8 +164,10 @@ class _ProductBlockState extends State<ProductBlock> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Product: ${widget.product.name}', style: const TextStyle(fontSize: 16)), Text('Product: ${widget.product.name}',
Text('SKU: ${widget.product.sku}', style: const TextStyle(fontSize: 15)), style: const TextStyle(fontSize: 16)),
Text('SKU: ${widget.product.sku}',
style: const TextStyle(fontSize: 15)),
const SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: saleController, controller: saleController,

View File

@ -1,6 +1,7 @@
import 'package:cheminova/services/secure__storage_service.dart'; import 'package:cheminova/services/secure__storage_service.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:cheminova/services/api_urls.dart'; import 'package:cheminova/services/api_urls.dart';
import 'package:flutter/material.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart';
class ApiClient { class ApiClient {
@ -19,6 +20,7 @@ class ApiClient {
.add(InterceptorsWrapper(onRequest: (options, handler) async { .add(InterceptorsWrapper(onRequest: (options, handler) async {
String? token = await _storageService.read(key: 'access_token'); String? token = await _storageService.read(key: 'access_token');
if (token != null) { if (token != null) {
debugPrint('Token start ------------> $token <------------ Token end');
options.headers['Authorization'] = 'Bearer $token'; options.headers['Authorization'] = 'Bearer $token';
} }
return handler.next(options); return handler.next(options);

View File

@ -14,5 +14,6 @@ class ApiUrls {
static const String notificationUrl = '${baseUrl}/get-notification-sc'; static const String notificationUrl = '${baseUrl}/get-notification-sc';
static const String fcmUrl = '${baseUrl}kyc/save-fcm-sc'; static const String fcmUrl = '${baseUrl}kyc/save-fcm-sc';
static const String getProducts = '${baseUrl}product/getAll/user'; static const String getProducts = '${baseUrl}product/getAll/user';
static const String getPdRdUrl = '${baseUrl}inventory/distributors-SC/';
static const String submitProductUrl = '${baseUrl}inventory/add-SC';
} }