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 =>
_scaffoldMessengerKey;
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

View File

@ -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<MyApp> {
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// scaffoldMessengerKey: SnackBarService().scaffoldMessengerKey,
navigatorKey: navigatorKey,
title: 'cheminova',
theme: ThemeData(
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 {
final bool success;
final int totalData;
final int totalPages;
final List<Product> product;
bool? success;
int? totalData;
int? totalPages;
List<Products>? 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<String, dynamic> 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<String, dynamic> json) {
success = json['success'];
totalData = json['total_data'];
totalPages = json['total_pages'];
if (json['products'] != null) {
products = <Products>[];
json['products'].forEach((v) {
products!.add(new Products.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
return {
'success': success,
'total_data': totalData,
'total_pages': totalPages,
'product': product.map((item) => item.toJson()).toList(),
};
final Map<String, dynamic> data = new Map<String, dynamic>();
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<dynamic> 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<Null>? 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<String, dynamic> 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<dynamic>,
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
v: json['__v'],
);
Products.fromJson(Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> json) {
return Category(
id: json['_id'],
categoryName: json['categoryName'],
);
Category.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
categoryName = json['categoryName'];
}
Map<String, dynamic> toJson() {
return {
'_id': id,
'categoryName': categoryName,
};
final Map<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> json) {
return GST(
id: json['_id'],
name: json['name'],
tax: json['tax'],
);
GST.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
name = json['name'];
tax = json['tax'];
}
Map<String, dynamic> toJson() {
return {
'_id': id,
'name': name,
'tax': tax,
};
final Map<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> json) {
return AddedBy(
id: json['_id'],
name: json['name'],
);
AddedBy.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
name = json['name'];
}
Map<String, dynamic> toJson() {
return {
'_id': id,
'name': name,
};
final Map<String, dynamic> data = new Map<String, dynamic>();
data['_id'] = this.sId;
data['name'] = this.name;
return data;
}
}
}

View File

@ -262,7 +262,7 @@ class CollectKycProvider extends ChangeNotifier {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DataSubmitSuccessfull()));
builder: (context) => const DataSubmitSuccessFullScreen()));
}
} else {
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_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<Product> productList = [];
List<ProductModel> productList = [];
List<ProductModel> searchList = [];
bool _isLoading = false;
bool get isLoading => _isLoading;
List<ProductModel> 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<void> 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<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) {
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_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<AddProductsScreen> createState() => _AddProductsScreenState();
}
class _AddProductsScreenState extends State<AddProductsScreen> {
List<Product> selectedProducts = [];
List<Product> filteredProducts = [];
final searchController = TextEditingController();
late ProductProvider provider;
late ProductProvider productProvider;
@override
void initState() {
productProvider = Provider.of<ProductProvider>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
productProvider.getProducts();
});
super.initState();
provider=Provider.of<ProductProvider>(context,listen: false);
provider.getProducts();
filteredProducts = provider.productList;
}
void filterProducts(String query) {
setState(() {
final provider = Provider.of<ProductProvider>(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<ProductProvider>(
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<ProductProvider>(
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<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,
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),
if (value.selectedProducts.isNotEmpty) ...[
const SizedBox(height: 16.0),
Consumer<ProductProvider>(
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<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 {
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
_ProductBlockState createState() => _ProductBlockState();
State<ProductBlock> createState() => _ProductBlockState();
}
class _ProductBlockState extends State<ProductBlock> {
@ -240,21 +266,36 @@ class _ProductBlockState extends State<ProductBlock> {
@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<ProductBlock> {
@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,
),
),
],
),
),
)
);
}
}

View File

@ -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<DataSubmitSuccessfull> createState() => DataSubmitSuccessfullState();
State<DataSubmitSuccessFullScreen> createState() => DataSubmitSuccessFullScreenState();
}
class DataSubmitSuccessfullState extends State<DataSubmitSuccessfull> {
class DataSubmitSuccessFullScreenState extends State<DataSubmitSuccessFullScreen> {
@override
void initState() {
super.initState();

View File

@ -118,7 +118,7 @@ class DisplaySalesScreenState extends State<DisplaySalesScreen> {
text: 'SUBMIT',
backgroundColor: const Color(0xff004791),
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
void initState() {
Provider.of<HomeProvider>(context, listen: false).getProfile();
super.initState();
notificationServices.requestNotificationPermission();
notificationServices. getDeviceToken().then((value) {
if (kDebugMode) {
print('Device Token: $value');
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<HomeProvider>(context, listen: false).getProfile();
notificationServices.requestNotificationPermission();
notificationServices.getDeviceToken().then((value) {
if (kDebugMode) {
print('Device Token: $value');
}
});
});
}

View File

@ -99,9 +99,7 @@ class ProductSalesDataState extends State<ProductSalesData> {
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(),)),
)

View File

@ -108,7 +108,7 @@ class SummaryScreenState extends State<SummaryScreen> {
text: 'VIEW DATA',
backgroundColor: const Color(0xff004791),
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: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<UpdateInventoryScreen> {
final List<Product> 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<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();
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<String>(
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<String>(
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<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]);
},
),
),
],
),
],
),
),
);
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<PdRdProvider>(
builder: (context, value, child) => Column(children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15.0, vertical: 25),
child: DropdownButtonFormField<String>(
decoration: const InputDecoration(
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder()),
value: value.selectedDistributorType,
items: [
'Principal Distributor',
'Retail Distributor'
].map((String type) {
return DropdownMenuItem<String>(
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<GetPdRdResponse>(
decoration: const InputDecoration(
fillColor: Colors.white,
filled: true,
border: OutlineInputBorder()),
value: value.selectedPdRd,
items: value.pdRdList.map((e) {
return DropdownMenuItem<GetPdRdResponse>(
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<PdRdProvider>(
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<ProductBlock> {
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<ProductBlock> {
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,

View File

@ -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);

View File

@ -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';
}