Compare commits

...

2 Commits

Author SHA1 Message Date
kratikpal
7d424edc11 Calendar API 2024-10-14 13:01:09 +05:30
kratikpal
5326f4bd86 Update sales data task 2024-09-11 16:56:08 +05:30
28 changed files with 1640 additions and 528 deletions

View File

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

View File

@ -1,5 +1,7 @@
import 'dart:developer';
import 'dart:io';
import 'package:cheminova/constants/constant.dart';
import 'package:cheminova/provider/add_sales_provider.dart';
import 'package:cheminova/provider/collect_kyc_provider.dart';
import 'package:cheminova/provider/pd_rd_provider.dart';
import 'package:cheminova/provider/product_manual_provider.dart';
@ -106,6 +108,7 @@ Future<void> main() async {
ChangeNotifierProvider(create: (_) => PdRdProvider()),
ChangeNotifierProvider(create: (_) => TaskProvider()),
ChangeNotifierProvider(create: (_) => ProductManualProvider()),
ChangeNotifierProvider(create: (_) => AddSalesProvider()),
],
child: const MyApp(),
),
@ -123,6 +126,7 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
// scaffoldMessengerKey: SnackBarService().scaffoldMessengerKey,
title: 'cheminova',

View File

@ -2,15 +2,16 @@ class PdRdResponseModel {
String? id;
String? uniqueId;
String? name;
String? tradeNameRd;
ShippingAddress? shippingAddress;
Kyc? kyc;
String? salesCoordinator; // Nullable property for SalesCoordinator
PdRdResponseModel({
this.id,
this.uniqueId,
this.name,
this.tradeNameRd,
this.kyc,
this.shippingAddress,
this.salesCoordinator, // Initialize SalesCoordinator
});
@ -20,7 +21,7 @@ class PdRdResponseModel {
id: json["_id"],
name: json["name"],
uniqueId: json["uniqueId"],
tradeNameRd: json["trade_name"],
kyc: json["kyc"] != null ? Kyc.fromJson(json["kyc"]) : null,
shippingAddress: json['shippingAddress'] != null
? ShippingAddress.fromJson(json['shippingAddress'])
: null,
@ -70,3 +71,53 @@ class ShippingAddress {
);
}
}
class Kyc {
final String id;
final String street;
final String city;
final String state;
final String postalCode;
final String country;
final String panNumber;
final String tradeName;
final String gstNumber;
final bool isDefault;
final String? panImgUrl; // New field for PAN image URL
final String? aadharImgUrl; // New field for Aadhar image URL
final String? gstImgUrl; // New field for GST image URL
Kyc({
required this.id,
required this.street,
required this.city,
required this.state,
required this.postalCode,
required this.country,
required this.panNumber,
required this.tradeName,
required this.gstNumber,
required this.isDefault,
this.panImgUrl,
this.aadharImgUrl,
this.gstImgUrl,
});
factory Kyc.fromJson(Map<String, dynamic> json) {
return Kyc(
id: json['_id'] ?? '',
street: json['street'] ?? '',
city: json['city'] ?? '',
state: json['state'] ?? '',
postalCode: json['postalCode'] ?? '',
country: json['country'] ?? '',
panNumber: json['panNumber'] ?? '',
tradeName: json['trade_name'] ?? '',
gstNumber: json['gstNumber'] ?? '',
isDefault: json['isDefault'] ?? false,
panImgUrl: json['pan_img']?['url'],
aadharImgUrl: json['aadhar_img']?['url'],
gstImgUrl: json['gst_img']?['url'],
);
}
}

View File

@ -0,0 +1,174 @@
class SalesTaskResponse {
bool? success;
int? totalData;
int? totalPages;
List<SalesProduct>? products;
SalesTaskResponse(
{this.success, this.totalData, this.totalPages, this.products});
SalesTaskResponse.fromJson(Map<String, dynamic> json) {
success = json['success'];
totalData = json['total_data'];
totalPages = json['total_pages'];
if (json['products'] != null) {
products = <SalesProduct>[];
json['products'].forEach((v) {
products!.add(SalesProduct.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['success'] = success;
data['total_data'] = totalData;
data['total_pages'] = totalPages;
if (products != null) {
data['products'] = products!.map((v) => v.toJson()).toList();
}
return data;
}
}
class SalesProduct {
String? sId;
String? SKU;
String? ProductName;
Category? category;
Brand? brand;
int? price;
int? gST;
int? hSNCode;
String? description;
String? productStatus;
AddedBy? addedBy;
List<Null>? image;
String? createdAt;
String? updatedAt;
int? SalesAmount;
int? QuantitySold;
String? comments;
int? iV;
SalesProduct(
{this.sId,
this.SKU,
this.ProductName,
this.category,
this.brand,
this.price,
this.gST,
this.hSNCode,
this.description,
this.productStatus,
this.addedBy,
this.image,
this.createdAt,
this.updatedAt,
this.QuantitySold,
this.comments,
this.SalesAmount,
this.iV});
SalesProduct.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
SKU = json['SKU'];
ProductName = json['name'];
category =
json['category'] != null ? Category.fromJson(json['category']) : null;
brand = json['brand'] != null ? Brand.fromJson(json['brand']) : null;
price = json['price'];
gST = json['GST'];
hSNCode = json['HSN_Code'];
description = json['description'];
productStatus = json['product_Status'];
addedBy =
json['addedBy'] != null ? AddedBy.fromJson(json['addedBy']) : null;
createdAt = json['createdAt'];
updatedAt = json['updatedAt'];
iV = json['__v'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['_id'] = sId;
data['SKU'] = SKU;
data['name'] = ProductName;
if (category != null) {
data['category'] = category!.toJson();
}
if (brand != null) {
data['brand'] = brand!.toJson();
}
data['price'] = price;
data['GST'] = gST;
data['HSN_Code'] = hSNCode;
data['description'] = description;
data['product_Status'] = productStatus;
if (addedBy != null) {
data['addedBy'] = addedBy!.toJson();
}
data['createdAt'] = createdAt;
data['updatedAt'] = updatedAt;
data['__v'] = iV;
return data;
}
}
class Category {
String? sId;
String? categoryName;
Category({this.sId, this.categoryName});
Category.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
categoryName = json['categoryName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['_id'] = sId;
data['categoryName'] = categoryName;
return data;
}
}
class Brand {
String? sId;
String? brandName;
Brand({this.sId, this.brandName});
Brand.fromJson(Map<String, dynamic> json) {
sId = json['_id'];
brandName = json['brandName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['_id'] = sId;
data['brandName'] = brandName;
return data;
}
}
class AddedBy {
String? sId;
String? name;
AddedBy({this.sId, this.name});
AddedBy.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

@ -0,0 +1,105 @@
import 'dart:convert';
import 'package:cheminova/constants/constant.dart';
import 'package:cheminova/models/sales_task_response.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:dio/dio.dart';
import 'package:flutter/material.dart';
class AddSalesProvider with ChangeNotifier {
final _apiClient = ApiClient();
List<SalesProduct> tasksList = [];
List<SalesProduct> searchList = [];
bool _isLoading = false;
bool get isLoading => _isLoading;
List<SalesProduct> selectedProducts = [];
void setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
Future<void> getTask() async {
setLoading(true);
try {
Response response = await _apiClient.get(ApiUrls.salesTaskUrl);
setLoading(false);
if (response.statusCode == 200) {
final data = SalesTaskResponse.fromJson(response.data);
tasksList = data.products ?? [];
notifyListeners();
}
} catch (e) {
setLoading(false);
debugPrint("Error occurred while fetching sales tasks: $e");
}
}
void filterProducts(String val) {
tasksList = tasksList.where((element) {
final productNameLower = element.ProductName!.toLowerCase();
final productSkuLower = element.SKU!.toLowerCase();
final searchLower = val.toLowerCase();
return productNameLower.contains(searchLower) ||
productSkuLower.contains(searchLower);
}).toList();
notifyListeners();
}
Future<void> submitProducts(
{required String distributorType,
required String pdRdId,
required String date,
String? inventoryId,
required String tradeName}) async {
setLoading(true);
try {
Response response = await _apiClient.post(ApiUrls.postSalesTaskUrl,
data: json.encode({
"addedFor": distributorType.replaceAll(' ', ''),
"addedForId": pdRdId,
"tradename": tradeName,
"date": date,
"products": selectedProducts.map((product) {
return {
"SKU": product.SKU,
"ProductName": product.ProductName,
"QuantitySold": product.QuantitySold,
"comments": product.comments,
"SalesAmount": product.SalesAmount
};
}).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 DataSubmitSuccessfull(),
),
);
}
} catch (e) {
setLoading(false);
debugPrint("Error: $e");
}
}
void resetProducts() {
selectedProducts.clear();
tasksList.clear();
notifyListeners();
}
}

View File

@ -30,6 +30,7 @@ class CollectKycProvider extends ChangeNotifier {
final aadharNumberController = TextEditingController();
final panNumberController = TextEditingController();
final gstNumberController = TextEditingController();
final emailController = TextEditingController();
String? selectedDistributor;
@ -210,6 +211,12 @@ class CollectKycProvider extends ChangeNotifier {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Selfie of Entrance Board is required')),
);
} else if (emailController.text.trim().isEmpty ||
!emailController.text.trim().contains(RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"))) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invalid email address')),
);
} else {
submitCollectKycForm(context);
}
@ -221,6 +228,7 @@ class CollectKycProvider extends ChangeNotifier {
'name': nameController.text.trim(),
'trade_name': tradeNameController.text.trim(),
'address': addressController.text.trim(),
'email': emailController.text.trim(),
'state': state.text.trim(),
'city': city.text.trim(),
'district': districtController.text.trim(),
@ -267,8 +275,13 @@ class CollectKycProvider extends ChangeNotifier {
}
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Submission failed: ${response.statusMessage}')));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'${response.data['message'] ?? 'Something went wrong'}',
),
),
);
}
}
} catch (e) {

View File

@ -52,6 +52,7 @@ class ProductProvider extends ChangeNotifier {
"ProductName": product.name,
"Sale": product.sale,
"Inventory": product.inventory,
"productid": product.id,
};
}).toList(),
"addedFor": addedFor,

View File

@ -112,7 +112,7 @@ class TaskProvider extends ChangeNotifier {
'addedForId': _selectedDistributor!.id,
'tradename': selectedDistributorType == 'PrincipalDistributor'
? _selectedDistributor!.shippingAddress!.tradeName
: _selectedDistributor!.tradeNameRd,
: _selectedDistributor!.kyc!.tradeName,
});
}
@ -180,7 +180,7 @@ class TaskProvider extends ChangeNotifier {
.map((json) => PdRdResponseModel.fromJson(json))
.toList();
_rdList = data;
print("RDTradeName ${data[0].tradeNameRd}");
print("RDTradeName ${data[0].kyc!.tradeName}");
} else {
print("Failed to load data: ${response.statusCode}");
}
@ -245,4 +245,24 @@ class TaskProvider extends ChangeNotifier {
setLoading(false);
}
}
Future<void> getAllTaskByDate(DateTime date) async {
clear();
setLoading(true);
try {
final String formatedDate = DateFormat('dd/MM/yyyy').format(date);
Response response =
await _apiClient.get("${ApiUrls.allTaskByDate}?Date=$formatedDate");
if (response.statusCode == 200) {
List<TaskModel> data = (response.data['tasks'] as List)
.map((json) => TaskModel.fromJson(json))
.toList();
_taskModelList = data;
}
} catch (e) {
print("Error occurred: $e");
} finally {
setLoading(false);
}
}
}

View File

@ -1,32 +1,52 @@
import 'package:cheminova/screens/data_submit_successfull.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../constants/constant.dart';
import '../screens/data_submit_successfull.dart';
import '../services/api_client.dart';
import '../services/api_urls.dart';
class VisitPdRdProvider with ChangeNotifier {
final _apiClient = ApiClient();
bool _isLoading = false;
bool get isLoading => _isLoading;
void setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
Future<void> submitVisitPdRd(BuildContext context, String id) async {
bool get isLoading => _isLoading;
Future<void> submitVisitPdRd({
required String pdRdId,
required String distributorType,
required String tradeName,
required String visitDate,
required String visitTime,
required String visitPurpose,
required String meetingSummary,
String? followUp,
String? nextVisitDate,
}) async {
setLoading(true);
try {
Response response =
await _apiClient.put(ApiUrls.updateTaskInventoryUrl + id);
debugPrint('Response: $response');
Response response = await _apiClient.post(ApiUrls.visitRdPd, data: {
"addedFor": distributorType,
"addedForId": pdRdId,
"tradename": tradeName,
"visitDate": visitDate,
"visitTime": visitTime,
"visitpurpose": visitPurpose,
"meetingsummary": meetingSummary,
if (followUp != null) "followup": followUp,
if (nextVisitDate != null) "nextvisitdate": nextVisitDate
});
setLoading(false);
if (response.statusCode == 200) {
if (response.statusCode == 201) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DataSubmitSuccessfull()));
navigatorKey.currentContext!,
MaterialPageRoute(
builder: (context) => const DataSubmitSuccessfull(),
),
);
}
} catch (e) {
setLoading(false);

View File

@ -1,204 +0,0 @@
import 'package:cheminova/provider/visit_pdrd_provider.dart';
import 'package:cheminova/widgets/common_drawer.dart';
import 'package:flutter/material.dart';
import 'package:cheminova/widgets/common_background.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../widgets/common_app_bar.dart';
import '../widgets/common_elevated_button.dart';
import '../widgets/common_text_form_field.dart';
import 'package:image_picker/image_picker.dart';
class VisitDealersScreen extends StatefulWidget {
final String? tradeName;
final String? id;
const VisitDealersScreen({super.key, this.tradeName, this.id});
@override
State<VisitDealersScreen> createState() => VisitDealersScreenState();
}
class VisitDealersScreenState extends State<VisitDealersScreen> {
late VisitPdRdProvider _visitPdRdProvider;
final dateController = TextEditingController(
text: DateFormat('dd/MM/yyyy').format(DateTime.now()));
final timeController =
TextEditingController(text: DateFormat('hh:mm a').format(DateTime.now()));
final notesController = TextEditingController();
final dealerController = TextEditingController();
final meetingSummaryController = TextEditingController();
final followUpActionsController = TextEditingController();
final nextVisitDateController = TextEditingController();
late TextEditingController retailerController = TextEditingController();
String selectedPurpose = 'Sales';
List<String> purposeOptions = ['Sales', 'Dues collection', 'Others'];
Future<void> _pickImage() async {
final ImagePicker picker = ImagePicker();
await picker.pickImage(source: ImageSource.camera);
// Handle the picked image
}
@override
void initState() {
_visitPdRdProvider = VisitPdRdProvider();
super.initState();
}
@override
Widget build(BuildContext context) {
retailerController = TextEditingController(text: widget.tradeName);
return ChangeNotifierProvider(
create: (context) => _visitPdRdProvider,
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: Column(
children: [
const Text('Visit Retailers',
style: TextStyle(
fontSize: 20,
color: Colors.black,
fontWeight: FontWeight.w400,
fontFamily: 'Anek')),
Text(widget.tradeName ?? '',
style: const TextStyle(
fontSize: 20,
color: Colors.black,
fontWeight: FontWeight.w400,
fontFamily: 'Anek')),
],
),
backgroundColor: Colors.transparent,
elevation: 0,
),
drawer: const CommonDrawer(),
body: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 16),
Container(
padding:
const EdgeInsets.all(20.0).copyWith(top: 30, bottom: 30),
margin: const EdgeInsets.symmetric(horizontal: 16.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
color: const Color(0xffB4D1E5).withOpacity(0.9),
borderRadius: BorderRadius.circular(26.0)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CommonTextFormField(
readOnly: true,
title: 'Select Retailer',
fillColor: Colors.white,
controller: retailerController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Visit date',
readOnly: true,
fillColor: Colors.white,
controller: dateController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Time',
readOnly: true,
fillColor: Colors.white,
controller: timeController),
const SizedBox(height: 15),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Purpose of visit',
fillColor: Colors.white,
filled: true,
),
value: selectedPurpose,
items: purposeOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
selectedPurpose = newValue!;
});
},
),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Meeting Summary:',
fillColor: Colors.white,
maxLines: 4,
controller: meetingSummaryController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Follow-up Actions:',
fillColor: Colors.white,
maxLines: 4,
controller: followUpActionsController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Next visit date:',
readOnly: true,
fillColor: Colors.white,
controller: nextVisitDateController),
const SizedBox(height: 15),
Row(
children: [
Expanded(
child: CommonTextFormField(
title: 'Attach Documents/Photos',
fillColor: Colors.white,
controller: notesController),
),
IconButton(
icon: const Icon(Icons.camera_alt),
onPressed: _pickImage,
),
],
),
const SizedBox(height: 15),
Consumer<VisitPdRdProvider>(
builder: (context, value, child) => Align(
alignment: Alignment.center,
child: CommonElevatedButton(
borderRadius: 30,
width: double.infinity,
height: kToolbarHeight - 10,
text: 'SUBMIT',
backgroundColor: const Color(0xff004791),
onPressed: () {
value.submitVisitPdRd(context, widget.id ?? '');
},
),
),
),
],
),
),
],
),
),
),
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:cheminova/models/pd_rd_response_model.dart';
import 'package:cheminova/models/product_model.dart';
import 'package:cheminova/provider/product_provider.dart';
import 'package:cheminova/screens/data_submit_successfull.dart';
import 'package:cheminova/utils/string_extension.dart';
import 'package:cheminova/widgets/common_app_bar.dart';
import 'package:cheminova/widgets/common_background.dart';
import 'package:cheminova/widgets/common_drawer.dart';
@ -72,7 +73,7 @@ class _AddProductsScreenState extends State<AddProductsScreen> {
),
],
title: Text(
widget.distributor.name!,
widget.distributor.name!.capitalize(),
style: const TextStyle(
fontSize: 20,
color: Colors.black,
@ -160,7 +161,8 @@ class _AddProductsScreenState extends State<AddProductsScreen> {
child: ListTile(
title: Text(
filteredProducts[index]
.name,
.name
.capitalize(),
style: TextStyle(
color: isAlreadySelected
? Colors.grey
@ -221,7 +223,10 @@ class _AddProductsScreenState extends State<AddProductsScreen> {
onPressed: () {
provider
.submitSelectedProducts(
"PrincipalDistributor",
(widget.distributorType ==
'Principal Distributor')
? 'PrincipalDistributor'
: 'RetailDistributor',
widget.distributor.id!)
.then((value) {
if (value) {
@ -232,6 +237,12 @@ class _AddProductsScreenState extends State<AddProductsScreen> {
const DataSubmitSuccessfull(),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Insufficient stock"),
),
);
}
});
},
@ -277,15 +288,8 @@ class _ProductBlockState extends State<ProductBlock> {
inventoryController.text.isNotEmpty) {
// Check if both inputs are valid integers
try {
int sale = int.parse(saleController.text);
int inventory = int.parse(inventoryController.text);
// Validation: inventory should be less than or equal to sales
if (inventory > sale) {
errorMessage = 'Inventory should be less than or equal to sales';
} else {
errorMessage = null;
}
int.parse(saleController.text);
int.parse(inventoryController.text);
} catch (e) {
// Handle the case where input is not a valid integer
errorMessage = 'Please enter valid integer values for both fields';
@ -308,7 +312,8 @@ class _ProductBlockState extends State<ProductBlock> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Product: ${provider.selectedProducts[widget.index].name}',
Text(
'Product: ${provider.selectedProducts[widget.index].name.capitalize()}',
style: const TextStyle(fontSize: 16)),
Text('SKU: ${provider.selectedProducts[widget.index].SKU}',
style: const TextStyle(fontSize: 15)),

View File

@ -0,0 +1,463 @@
import 'package:cheminova/models/sales_task_response.dart';
import 'package:cheminova/provider/add_sales_provider.dart';
import 'package:cheminova/utils/string_extension.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:intl/intl.dart';
import 'package:provider/provider.dart';
class AddSalesProductScreen extends StatefulWidget {
final String distributorType;
final String tradeName;
final String pdRdId;
final String? inventoryId;
const AddSalesProductScreen(
{super.key,
required this.distributorType,
required this.tradeName,
required this.pdRdId,
this.inventoryId});
@override
State<AddSalesProductScreen> createState() => _AddSalesProductScreenState();
}
class _AddSalesProductScreenState extends State<AddSalesProductScreen> {
final searchController = TextEditingController();
late AddSalesProvider salesTaskProvider;
final dateController = TextEditingController();
final formKey = GlobalKey<FormState>();
@override
void initState() {
salesTaskProvider = Provider.of<AddSalesProvider>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
salesTaskProvider.getTask();
});
super.initState();
}
@override
void dispose() {
if (mounted) {
salesTaskProvider.resetProducts();
}
super.dispose();
}
datePicker() async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2101),
);
if (picked != null) {
setState(
() => dateController.text = DateFormat('dd/MM/yyyy').format(picked));
}
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: true,
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: Text(
'${widget.distributorType}\n${widget.tradeName.capitalize()}',
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<AddSalesProvider>(
builder: (context, value, child) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: value.tasksList.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<AddSalesProvider>(
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.tasksList.length
: value.searchList.length,
itemBuilder: (context, index) {
bool isAlreadySelected = value
.selectedProducts
.any((selectedProduct) =>
selectedProduct.SKU ==
value.tasksList[index]
.SKU);
final data = searchController
.text.isEmpty
? value.tasksList[index]
: value.searchList[index];
return Card(
child: ListTile(
title: Text(
data.ProductName
?.capitalize() ??
'',
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))),
if (value.selectedProducts.isNotEmpty) ...[
const SizedBox(height: 16.0),
Consumer<AddSalesProvider>(
builder: (context, value, child) =>
CommonElevatedButton(
borderRadius: 30,
width: double.infinity,
height: kToolbarHeight - 10,
text: 'SUBMIT',
backgroundColor: const Color(0xff004791),
onPressed: () {
if (formKey.currentState!.validate()) {
if (value.selectedProducts.isNotEmpty &&
value.selectedProducts.every((product) =>
product.SKU!.isNotEmpty &&
product.ProductName!.isNotEmpty &&
product.SalesAmount != null &&
product.QuantitySold != null)) {
value.submitProducts(
distributorType: widget.distributorType,
pdRdId: widget.pdRdId,
inventoryId: widget.inventoryId,
date: dateController.text.trim(),
tradeName: widget.tradeName);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please fill out all product details, including sale and inventory.',
),
),
);
}
}
},
),
),
]
],
),
),
),
],
),
),
body: Consumer<AddSalesProvider>(
builder: (context, value, child) {
return Stack(
children: [
Column(
children: [
GestureDetector(
onTap: () => datePicker(),
child: AbsorbPointer(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: formKey,
child: TextFormField(
controller: dateController,
validator: (value) {
if (value!.isEmpty) {
return 'Please select a date';
}
return null;
},
decoration: const InputDecoration(
labelText: 'Date',
fillColor: Colors.white,
filled: true,
border: InputBorder.none,
suffixIcon: Icon(Icons.calendar_today),
),
),
),
),
),
),
if (value.selectedProducts.isNotEmpty)
Expanded(
child: ListView.builder(
itemCount: value.selectedProducts.length,
itemBuilder: (context, index) {
return ProductBlock(
onUpdate: (updatedProduct) {
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 SalesProduct product;
final ValueChanged<SalesProduct> onUpdate;
final VoidCallback onRemove;
const ProductBlock({
super.key,
required this.product,
required this.onUpdate,
required this.onRemove,
});
@override
State<ProductBlock> createState() => _ProductBlockState();
}
class _ProductBlockState extends State<ProductBlock> {
final saleAmountController = TextEditingController();
final quantitySoldController = TextEditingController();
final commentController = TextEditingController();
String? errorMessage;
@override
void initState() {
super.initState();
saleAmountController.text = (widget.product.SalesAmount ?? '').toString();
commentController.text = (widget.product.comments ?? '').toString();
quantitySoldController.text =
(widget.product.QuantitySold ?? '').toString();
}
void validateInput() {
setState(() {
String? quantitySoldError;
String? salesAmountError;
if (saleAmountController.text.isEmpty) {
quantitySoldError = 'Quantity sold cannot be empty.';
}
if (quantitySoldController.text.isEmpty) {
salesAmountError = 'Sales amount cannot be empty.';
}
errorMessage = null;
if (quantitySoldError == null && salesAmountError == null) {
int quantitySold = int.parse(quantitySoldController.text);
int salesAmount = int.parse(saleAmountController.text);
String comments = commentController.text;
widget.onUpdate(SalesProduct(
SKU: widget.product.SKU,
ProductName: widget.product.ProductName,
QuantitySold: quantitySold,
SalesAmount: salesAmount,
comments: comments,
));
} else {
errorMessage = quantitySoldError ?? salesAmountError;
}
});
}
@override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
margin: const EdgeInsets.all(8),
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(16),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('Product: ${widget.product.ProductName!.capitalize()}',
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: saleAmountController,
onTapOutside: (event) => FocusScope.of(context).unfocus(),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
labelText: 'Sales amount',
errorText: saleAmountController.text.isEmpty
? 'Sales amount cannot be empty.'
: null),
keyboardType: TextInputType.number,
enabled: true,
onChanged: (_) => validateInput()),
TextField(
controller: quantitySoldController,
onTapOutside: (event) => FocusScope.of(context).unfocus(),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
labelText: 'Quantity sold',
errorText: quantitySoldController.text.isEmpty
? 'Quantity sold cannot be empty.'
: null),
keyboardType: TextInputType.number,
enabled: true,
onChanged: (_) => validateInput()),
TextField(
controller: commentController,
onTapOutside: (event) => FocusScope.of(context).unfocus(),
decoration: const InputDecoration(
labelText: 'Comments',
),
enabled: true,
onChanged: (_) => validateInput())
]),
]),
),
Positioned(
top: 0,
right: 0,
child: IconButton(
icon: const Icon(
Icons.delete_outlined,
color: Colors.red,
),
onPressed: widget.onRemove)),
],
),
);
}
}
class ProductModel {
final String sku;
final String productName;
final int? sale;
final int? inventory;
final String? comments;
final String? date;
ProductModel({
required this.sku,
required this.productName,
this.sale,
this.inventory,
this.comments,
this.date,
});
}

View File

@ -119,15 +119,15 @@ class _AssignTaskDashBoardScreenState extends State<AssignTaskDashBoardScreen> {
),
),
),
const SizedBox(height: 15),
CommonElevatedButton(
backgroundColor: const Color(0xff004791),
borderRadius: 30,
width: double.infinity,
height: kToolbarHeight - 10,
text: 'MANAGE SCs',
onPressed: () {},
),
// const SizedBox(height: 15),
// CommonElevatedButton(
// backgroundColor: const Color(0xff004791),
// borderRadius: 30,
// width: double.infinity,
// height: kToolbarHeight - 10,
// text: 'MANAGE SCs',
// onPressed: () {},
// ),
],
),
),

View File

@ -1,6 +1,7 @@
import 'package:cheminova/models/pd_rd_response_model.dart';
import 'package:cheminova/provider/task_provider.dart';
import 'package:cheminova/screens/confirm_task_screen.dart';
import 'package:cheminova/utils/string_extension.dart';
import 'package:provider/provider.dart';
import 'package:cheminova/widgets/common_app_bar.dart';
import 'package:cheminova/widgets/common_background.dart';
@ -149,15 +150,6 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Assign Tasks',
style: TextStyle(
fontSize: 24,
color: Colors.white,
fontWeight: FontWeight.bold,
fontFamily: 'Anek',
),
),
const SizedBox(height: 20),
SearchField(
suggestionsDecoration: SuggestionDecoration(
@ -166,7 +158,7 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
bottomRight: Radius.circular(8),
),
border: Border.all(
color: Colors.grey.withOpacity(0.5),
color: Colors.white.withOpacity(0.5),
),
),
suggestionItemDecoration: BoxDecoration(
@ -216,7 +208,7 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
(e) => SearchFieldListItem(
e.name!,
item: e,
child: Text(e.name!),
child: Text(e.name!.capitalize()),
),
)
.toList(),
@ -266,7 +258,7 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
taskProvider.setSelectedTask(value);
},
title: const Text(
"Update Sales Data Data",
"Update Sales Data",
),
),
),
@ -321,6 +313,7 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
),
child: TextFormField(
controller: taskProvider.noteController,
textCapitalization: TextCapitalization.sentences,
expands: true,
maxLines: null,
minLines: null,
@ -345,11 +338,12 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
if (taskProvider.selectedTask ==
'Update Inventory Data' ||
taskProvider.selectedTask == 'Visit RD/PD' ||
taskProvider.selectedTask == 'Update Sales Data') ...{
taskProvider.selectedTask ==
'Update Sales Data') ...{
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15.0,
vertical: 5,
horizontal: 5.0,
vertical: 15,
),
child: DropdownButtonFormField<String>(
decoration: const InputDecoration(
@ -384,7 +378,7 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
if (selectedDistributorType != null)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15.0, vertical: 25),
horizontal: 5.0, vertical: 15),
child: DropdownButtonFormField<PdRdResponseModel>(
decoration: const InputDecoration(
labelText: 'Select Distributor Name',
@ -400,7 +394,15 @@ class _AssignTasksScreenState extends State<AssignTasksScreen> {
.map((PdRdResponseModel distributor) {
return DropdownMenuItem<PdRdResponseModel>(
value: distributor,
child: Text(distributor.name!),
child: Text(
(selectedDistributorType ==
'PrincipalDistributor'
? distributor
.shippingAddress!.tradeName
.capitalize()
: distributor.kyc!.tradeName
.capitalize()),
),
);
}).toList(),
onChanged: (value) {

View File

@ -1,44 +1,61 @@
import 'package:cheminova/models/task_model.dart';
import 'package:cheminova/provider/task_provider.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:table_calendar/table_calendar.dart';
import '../widgets/common_app_bar.dart';
import '../widgets/common_background.dart';
import '../widgets/common_drawer.dart';
DateTime _focusedDay=DateTime.now();
DateTime? _selectedDay=DateTime.now();
DateTime _focusedDay = DateTime.now();
DateTime _selectedDay = DateTime.now();
class CalendarScreen extends StatelessWidget {
const CalendarScreen({super.key});
@override
Widget build(BuildContext context) {
return CommonBackground(
final taskProvider = context.watch<TaskProvider>();
return CommonBackground(
child: Scaffold(
appBar: CommonAppBar(
actions: [
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Image.asset('assets/Back_attendance.png'),
padding: const EdgeInsets.only(right: 20),
),
],
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Image.asset('assets/Back_attendance.png'),
padding: const EdgeInsets.only(right: 20),
),
],
title: const Text('Calendar'),
backgroundColor: Colors.transparent,
elevation: 0,
),
backgroundColor: Colors.transparent,
drawer: const CommonDrawer(),
body: const SingleChildScrollView(
child: Column(
children: [
CalendarWidget(),
EventsList(),
],
),
body: Stack(
children: [
const Column(
children: [
CalendarWidget(),
TaskList(),
],
),
if (taskProvider.isLoading)
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.2),
),
child: const Center(
child: CircularProgressIndicator(),
),
),
],
),
),
);
@ -59,110 +76,137 @@ class _CalendarWidgetState extends State<CalendarWidget> {
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Calendar',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Text(
'Month/Year',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
const SizedBox(height: 10),
TableCalendar(
firstDay: DateTime.utc(1900, 5, 1),
lastDay: DateTime.utc(2900, 5, 1),
focusedDay: _focusedDay,
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
),
],
child: TableCalendar(
firstDay: DateTime.utc(1900, 5, 1),
lastDay: DateTime.utc(2900, 5, 1),
focusedDay: _focusedDay,
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
context.read<TaskProvider>().getAllTaskByDate(_selectedDay);
});
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
),
),
);
}
}
class EventsList extends StatelessWidget {
const EventsList({super.key});
class TaskList extends StatefulWidget {
const TaskList({super.key});
@override
State<TaskList> createState() => _TaskListState();
}
class _TaskListState extends State<TaskList> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<TaskProvider>().getAllTaskByDate(_selectedDay);
});
}
@override
Widget build(BuildContext context) {
return const Card(
margin: EdgeInsets.all(16),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Events List',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
final taskProvider = context.watch<TaskProvider>();
return (taskProvider.taskModelList.isEmpty)
? Container(
alignment: Alignment.center,
child: const Text('No task found',
style: TextStyle(fontSize: 20, color: Colors.white)),
)
: Expanded(
child: ListView.builder(
itemCount: taskProvider.taskModelList.length,
itemBuilder: (context, index) {
return _taskView(task: taskProvider.taskModelList[index]);
},
),
SizedBox(height: 10),
EventItem(
date: '10-06-2024',
event: 'Meeting with Territory Manager',
),
SizedBox(height: 10),
EventItem(
date: '10-06-2024',
event: 'Sales Data Review',
),
],
);
}
Widget _customContainer({required Widget child}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
color: const Color(0xffB4D1E5).withOpacity(0.8),
borderRadius: BorderRadius.circular(16.0),
),
child: child,
),
);
}
Widget _taskView({required TaskModel task}) {
final formattedDate = DateFormat('dd/MM/yyyy').format(task.taskDueDate);
return Column(
children: [
_customContainer(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Assigned to: ${task.taskAssignedTo.name}",
style: const TextStyle(
fontFamily: 'Anek',
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 5),
Text(
"Task: ${task.task}",
style: const TextStyle(
fontFamily: 'Anek',
),
),
const SizedBox(height: 5),
if (task.addedFor != null) ...{
task.addedFor == 'PrincipalDistributor'
? Text("PD: ${task.tradename}")
: Text("RD: ${task.tradename}"),
const SizedBox(height: 5),
},
Text(
'Status: ${task.taskStatus}',
style: const TextStyle(
fontFamily: 'Anek',
),
),
const SizedBox(height: 5),
if (task.note != null) ...{
Text(
'Note: ${task.note}',
style: const TextStyle(
fontFamily: 'Anek',
),
),
const SizedBox(height: 5),
},
Text(
'Deadline: $formattedDate',
style: const TextStyle(
fontFamily: 'Anek',
),
),
],
),
),
const SizedBox(height: 10),
],
);
}
}
class EventItem extends StatelessWidget {
final String date;
final String event;
const EventItem({super.key, required this.date, required this.event});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Date: $date'),
Text(
'Event: $event',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () {
// Add view details functionality here
},
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.blue[800],
),
child: const Text('VIEW DETAILS'),
),
],
),
);
}
}

View File

@ -1,8 +1,8 @@
import 'package:cheminova/screens/collect_kyc_screen.dart';
import 'package:cheminova/screens/update_inventory_screen.dart';
import 'package:cheminova/screens/select_distributer_screen.dart';
import 'package:cheminova/screens/display_sales_screen.dart';
import 'package:cheminova/screens/visit_rd_pd_screen.dart';
import 'package:flutter/material.dart';
import 'package:cheminova/screens/visit_Dealers_screen.dart';
import 'package:cheminova/widgets/common_app_bar.dart';
import 'package:cheminova/widgets/common_drawer.dart';
import 'package:cheminova/widgets/common_background.dart';
@ -48,12 +48,14 @@ class DailyTasksScreen extends StatelessWidget {
'Visit Dealers/Retailers',
'',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const VisitDealersScreen(),
),
);
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => const VisitRdPdScreen(
// tradeName: 't',
// ),
// ),
// );
},
),
const SizedBox(height: 5),
@ -61,18 +63,41 @@ class DailyTasksScreen extends StatelessWidget {
'Update Sales Data',
'',
onTap: () {
Navigator.push(context,MaterialPageRoute(builder: (context) => const DisplaySalesScreen(),));
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const DisplaySalesScreen(),
));
},
),
const SizedBox(height: 5),
_buildCustomCard('Update Inventory Data', '',onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => const UpdateInventoryScreen(),));
},),
_buildCustomCard(
'Update Inventory Data',
'',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const SelectDistributerScreen(
task: "Add Products",
),
));
},
),
const SizedBox(height: 5),
_buildCustomCard('Collect KYC Documents', '',onTap:() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => const CollectKycScreen(),));
}, ),
_buildCustomCard(
'Collect KYC Documents',
'',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CollectKycScreen(),
));
},
),
],
),
),
@ -84,7 +109,8 @@ class DailyTasksScreen extends StatelessWidget {
);
}
Widget _buildCustomCard(String title, String subtitle, {void Function()? onTap}) {
Widget _buildCustomCard(String title, String subtitle,
{void Function()? onTap}) {
return Container(
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
@ -103,9 +129,9 @@ class DailyTasksScreen extends StatelessWidget {
),
subtitle: subtitle.isNotEmpty
? Text(
subtitle,
style: const TextStyle(color: Colors.white70, fontSize: 13),
)
subtitle,
style: const TextStyle(color: Colors.white70, fontSize: 13),
)
: null,
onTap: onTap,
),

View File

@ -1,6 +1,6 @@
import 'package:cheminova/notification_service.dart';
import 'package:cheminova/provider/user_provider.dart';
import 'package:cheminova/screens/Visit_Dealers_screen.dart';
import 'package:cheminova/screens/visit_rd_pd_screen.dart';
import 'package:cheminova/screens/assign_task_dash_board_screen.dart';
import 'package:cheminova/screens/calendar_screen.dart';
import 'package:cheminova/screens/collect_kyc_screen.dart';
@ -8,8 +8,7 @@ import 'package:cheminova/screens/mark_attendence_screen.dart';
import 'package:cheminova/screens/notification_screen.dart';
import 'package:cheminova/screens/products_manual_screen.dart';
import 'package:cheminova/screens/rejected_application_screen.dart';
import 'package:cheminova/screens/product_sales_data.dart';
import 'package:cheminova/screens/update_inventory_screen.dart';
import 'package:cheminova/screens/select_distributer_screen.dart';
import 'package:cheminova/screens/display_sales_screen.dart';
import 'package:cheminova/widgets/common_drawer.dart';
import 'package:flutter/material.dart';
@ -119,13 +118,13 @@ class _HomePageState extends State<HomePage> {
Row(
children: [
Expanded(
child: _buildCustomCard('Display\nSales data',
'Quickly display Sales\n', onTap: () {
child: _buildCustomCard('Collect \nKYC Data',
'Scan and upload KYC Documents', onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const DisplaySalesScreen(),
const CollectKycScreen(),
));
}),
),
@ -139,7 +138,9 @@ class _HomePageState extends State<HomePage> {
context,
MaterialPageRoute(
builder: (context) =>
const UpdateInventoryScreen(),
const SelectDistributerScreen(
task: "Update Inventory",
),
));
}),
),
@ -152,14 +153,16 @@ class _HomePageState extends State<HomePage> {
children: [
Expanded(
child: _buildCustomCard(
'Visit RD/PD',
'\n\n',
'Visit RD/PD\n\n',
'',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const VisitDealersScreen(),
const SelectDistributerScreen(
task: "Visit RD/PD",
),
),
);
},
@ -169,33 +172,17 @@ class _HomePageState extends State<HomePage> {
width: 12,
),
Expanded(
child: _buildCustomCard(
'Product\nSales Data Visibility', '',
child: _buildCustomCard('Update\nSales Data\n', '',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ProductSalesData(),
));
}),
),
],
),
const SizedBox(
height: 5,
),
Row(
children: [
Expanded(
child: _buildCustomCard('Collect \nKYC Data',
'Scan and upload KYC Documents', onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const CollectKycScreen(),
));
context,
MaterialPageRoute(
builder: (context) =>
const SelectDistributerScreen(
task: "Update Sales",
),
),
);
}),
),
],
@ -271,10 +258,6 @@ class _HomePageState extends State<HomePage> {
),
),
const SizedBox(height: 12),
TextButton(
onPressed: () => throw Exception(),
child: const Text("Throw Test Exception"),
),
],
),
),

View File

@ -94,81 +94,83 @@ class MyListView extends StatelessWidget {
RejectedApplicationResponse item = value.rejectedApplicationList[index];
return Padding(
padding: const EdgeInsets.only(bottom: 10, left: 10, right: 10),
child: ExpansionTile(
collapsedBackgroundColor: Colors.white,
backgroundColor: Colors.white,
title: Text(
item.tradeName ?? '',
style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text((DateFormat("dd MMMM yyyy")
.format(DateTime.parse(item.createdAt ?? '')))),
Text(item.sId ?? ''),
for (var note in item.notes!) Text(note.message ?? ''),
child: ClipRRect(
borderRadius: BorderRadius.circular(15), // Rounded corner
child: ExpansionTile(
collapsedBackgroundColor: Colors.white,
backgroundColor: Colors.white,
title: Text(
item.tradeName ?? '',
style:
const TextStyle(fontSize: 17, fontWeight: FontWeight.w500),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text((DateFormat("dd MMMM yyyy")
.format(DateTime.parse(item.createdAt ?? '')))),
Text("ID:${item.sId ?? ''}"),
for (var note in item.notes!) Text(note.message ?? ''),
],
),
children: <Widget>[
ListTile(
title: Text('Address: ${item.address ?? ''}'),
),
ListTile(
title: Text('City: ${item.city ?? ''}'),
),
ListTile(
title: Text('State: ${item.state ?? ''}'),
),
ListTile(
title: Text('Pincode: ${item.pincode ?? ''}'),
),
ListTile(
title: Text('Mobile: ${item.mobileNumber ?? ''}'),
),
ListTile(
title: Text('Status: ${item.status ?? ''}'),
),
ListTile(
title: Text(
'Principal Distributor: ${item.principalDistributer!.name ?? ''}'),
),
ListTile(
title: Text('PAN Number: ${item.panNumber ?? ''}'),
),
Image.network(item.panImg!.url ?? '', height: 250, width: 250),
ListTile(
title: Text('Aadhar Number: ${item.aadharNumber ?? ''}'),
),
Image.network(item.aadharImg?.url ?? '',
height: 250, width: 250),
ListTile(
title: Text('GST Number: ${item.gstNumber ?? ''}'),
),
Image.network(item.gstImg!.url ?? '', height: 250, width: 250),
const ListTile(
title: Text('Pesticide License: '),
),
if (item.pesticideLicenseImg != null)
Image.network(item.pesticideLicenseImg!.url ?? '',
height: 250, width: 250),
const ListTile(
title: Text('selfieEntranceImg: '),
),
Image.network(item.selfieEntranceImg!.url ?? '',
height: 250, width: 250),
const ListTile(
title: Text('Notes:'),
),
if (item.notes != null)
for (var note in item.notes!)
ListTile(
contentPadding: const EdgeInsets.only(left: 20),
title: Text(note.message ?? ''),
),
],
),
children: <Widget>[
ListTile(
title: Text('Address: ${item.address ?? ''}'),
),
ListTile(
title: Text('City: ${item.city ?? ''}'),
),
ListTile(
title: Text('State: ${item.state ?? ''}'),
),
ListTile(
title: Text('Pincode: ${item.pincode ?? ''}'),
),
ListTile(
title: Text('Mobile: ${item.mobileNumber ?? ''}'),
),
ListTile(
title: Text('Status: ${item.status ?? ''}'),
),
ListTile(
title: Text(
'Principal Distributor: ${item.principalDistributer!.name ?? ''}'),
),
ListTile(
title: Text('PAN Number: ${item.panNumber ?? ''}'),
),
Image.network(item.panImg!.url ?? '', height: 250, width: 250),
ListTile(
title: Text('Aadhar Number: ${item.aadharNumber ?? ''}'),
),
Image.network(item.aadharImg?.url ?? '', height: 250, width: 250),
ListTile(
title: Text('GST Number: ${item.gstNumber ?? ''}'),
),
Image.network(item.gstImg!.url ?? '', height: 250, width: 250),
const ListTile(
title: Text('Pesticide License: '),
),
if (item.pesticideLicenseImg != null)
Image.network(item.pesticideLicenseImg!.url ?? '',
height: 250, width: 250),
// if (item['fertilizer_license_img'] != null)
// Image.network(item['fertilizer_license_img']['url'] ?? ''),
const ListTile(
title: Text('selfieEntranceImg: '),
),
Image.network(item.selfieEntranceImg!.url ?? '',
height: 250, width: 250),
const ListTile(
title: Text('Notes:'),
),
if (item.notes != null)
for (var note in item.notes!)
ListTile(
contentPadding: const EdgeInsets.only(left: 20),
title: Text(note.message ?? ''),
),
],
),
);
},

View File

@ -1,11 +1,12 @@
import 'package:cheminova/models/get_pd_response.dart';
import 'package:cheminova/provider/collect_kyc_provider.dart';
import 'package:cheminova/utils/no_space_formatter.dart';
import 'package:csc_picker/csc_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../constants/only_uppercase.dart';
import '../utils/only_uppercase.dart';
import '../widgets/common_elevated_button.dart';
import '../widgets/common_text_form_field.dart';
@ -45,6 +46,7 @@ class RetailerDetailsScreenState extends State<RetailerDetailsScreen> {
children: <Widget>[
CommonTextFormField(
title: 'Trade Name',
textCapitalization: TextCapitalization.words,
fillColor: Colors.white,
validator: (String? value) {
if (value!.isEmpty) {
@ -56,6 +58,7 @@ class RetailerDetailsScreenState extends State<RetailerDetailsScreen> {
const SizedBox(height: 15),
CommonTextFormField(
title: 'Name',
textCapitalization: TextCapitalization.words,
fillColor: Colors.white,
validator: (String? value) {
if (value!.isEmpty) {
@ -65,6 +68,21 @@ class RetailerDetailsScreenState extends State<RetailerDetailsScreen> {
},
controller: value.nameController),
const SizedBox(height: 15),
CommonTextFormField(
// maxLength: 20,
title: 'Email',
fillColor: Colors.white,
inputFormatters: [
NoSpaceFormatter(),
],
validator: (String? value) {
if (value!.isEmpty) {
return 'Email cannot be empty';
}
return null;
},
controller: value.emailController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Address',
fillColor: Colors.white,
@ -110,6 +128,7 @@ class RetailerDetailsScreenState extends State<RetailerDetailsScreen> {
CommonTextFormField(
maxLength: 6,
title: 'Pincode',
keyboardType: TextInputType.number,
fillColor: Colors.white,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
@ -125,6 +144,7 @@ class RetailerDetailsScreenState extends State<RetailerDetailsScreen> {
CommonTextFormField(
maxLength: 10,
title: 'Mobile Number',
keyboardType: TextInputType.number,
fillColor: Colors.white,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
@ -140,6 +160,7 @@ class RetailerDetailsScreenState extends State<RetailerDetailsScreen> {
CommonTextFormField(
maxLength: 12,
title: 'Aadhar Number',
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],

View File

@ -1,6 +1,9 @@
import 'package:cheminova/models/pd_rd_response_model.dart';
import 'package:cheminova/provider/pd_rd_provider.dart';
import 'package:cheminova/screens/add_products_screen.dart';
import 'package:cheminova/screens/add_sales_products_screen.dart';
import 'package:cheminova/screens/visit_rd_pd_screen.dart';
import 'package:cheminova/utils/string_extension.dart';
import 'package:cheminova/widgets/common_background.dart';
import 'package:cheminova/widgets/common_app_bar.dart';
import 'package:cheminova/widgets/common_drawer.dart';
@ -8,14 +11,16 @@ import 'package:cheminova/widgets/common_elevated_button.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class UpdateInventoryScreen extends StatefulWidget {
const UpdateInventoryScreen({super.key});
class SelectDistributerScreen extends StatefulWidget {
final String task;
const SelectDistributerScreen({super.key, required this.task});
@override
State<UpdateInventoryScreen> createState() => _UpdateInventoryScreenState();
State<SelectDistributerScreen> createState() =>
_SelectDistributerScreenState();
}
class _UpdateInventoryScreenState extends State<UpdateInventoryScreen> {
class _SelectDistributerScreenState extends State<SelectDistributerScreen> {
String? selectedDistributorType;
PdRdResponseModel? selectedDistributor;
@ -45,7 +50,7 @@ class _UpdateInventoryScreenState extends State<UpdateInventoryScreen> {
),
],
title: const Text(
'Update Inventory Data',
'Select Distributor',
style: TextStyle(
fontSize: 20,
color: Colors.black,
@ -69,18 +74,47 @@ class _UpdateInventoryScreenState extends State<UpdateInventoryScreen> {
selectedDistributorType == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please select distributor type and distributor')),
content:
Text('Please select distributor type and distributor'),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddProductsScreen(
distributor: selectedDistributor!,
distributorType: selectedDistributorType!,
),
),
MaterialPageRoute(builder: (context) {
if (widget.task == 'Update Inventory') {
return AddProductsScreen(
distributor: selectedDistributor!,
distributorType: selectedDistributorType!,
);
} else if (widget.task == 'Update Sales') {
return AddSalesProductScreen(
distributorType:
selectedDistributorType == 'Principal Distributor'
? 'PrincipalDistributor'
: 'RetailDistributor',
tradeName: selectedDistributorType ==
'Principal Distributor'
? selectedDistributor!.shippingAddress!.tradeName
: selectedDistributor!.kyc!.tradeName,
pdRdId: selectedDistributor!.id!,
);
} else if (widget.task == 'Visit RD/PD') {
return VisitRdPdScreen(
distributorType:
selectedDistributorType == 'Principal Distributor'
? 'PrincipalDistributor'
: 'RetailDistributor',
tradeName: selectedDistributorType ==
'Principal Distributor'
? selectedDistributor!.shippingAddress!.tradeName
: selectedDistributor!.kyc!.tradeName,
pdRdId: selectedDistributor!.id!,
);
} else {
return Container();
}
}),
);
}
},
@ -143,7 +177,13 @@ class _UpdateInventoryScreenState extends State<UpdateInventoryScreen> {
.map((PdRdResponseModel distributor) {
return DropdownMenuItem<PdRdResponseModel>(
value: distributor,
child: Text(distributor.name!),
child: Text(
selectedDistributorType ==
'PrincipalDistributor'
? distributor.shippingAddress!.tradeName
.capitalize()
: distributor.kyc!.tradeName.capitalize(),
),
);
}).toList(),
onChanged: (value) {

View File

@ -198,7 +198,7 @@ class _TaskManagementScreenState extends State<TaskManagementScreen> {
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
color: const Color(0xffB4D1E5).withOpacity(0.6),
color: const Color(0xffB4D1E5).withOpacity(0.8),
borderRadius: BorderRadius.circular(16.0),
),
child: child,
@ -220,21 +220,38 @@ class _TaskManagementScreenState extends State<TaskManagementScreen> {
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 10),
const SizedBox(height: 5),
Text(
"Task: ${task.task}",
style: const TextStyle(
fontFamily: 'Anek',
),
),
const SizedBox(height: 10),
const SizedBox(height: 5),
if (task.addedFor != null) ...{
task.addedFor == 'PrincipalDistributor'
? Text(
"PD: ${task.tradename}",
)
: Text("RD: ${task.tradename}"),
const SizedBox(height: 5),
},
Text(
'Status: ${task.taskStatus}',
style: const TextStyle(
fontFamily: 'Anek',
),
),
const SizedBox(height: 10),
const SizedBox(height: 5),
if (task.note != null) ...{
Text(
'Note: ${task.note}',
style: const TextStyle(
fontFamily: 'Anek',
),
),
const SizedBox(height: 5),
},
Text(
'Deadline:$formatedDate',
style: const TextStyle(

View File

@ -28,6 +28,7 @@ class VerifyDocumentsScreen extends StatelessWidget {
{
'Trade Name': value.tradeNameController.text,
'Name': value.nameController.text,
'Email': value.emailController.text,
'Address': value.addressController.text,
'Town/City': value.city.text,
'District': value.districtController.text,

View File

@ -0,0 +1,294 @@
import 'package:cheminova/provider/visit_pdrd_provider.dart';
import 'package:cheminova/widgets/common_drawer.dart';
import 'package:flutter/material.dart';
import 'package:cheminova/widgets/common_background.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import '../widgets/common_app_bar.dart';
import '../widgets/common_elevated_button.dart';
import '../widgets/common_text_form_field.dart';
import 'package:image_picker/image_picker.dart';
class VisitRdPdScreen extends StatefulWidget {
final String? tradeName;
final String? pdRdId;
final String? distributorType;
const VisitRdPdScreen({
super.key,
required this.tradeName,
required this.pdRdId,
required this.distributorType,
});
@override
State<VisitRdPdScreen> createState() => VisitRdPdScreenState();
}
class VisitRdPdScreenState extends State<VisitRdPdScreen> {
late VisitPdRdProvider _visitPdRdProvider;
final dateController = TextEditingController(
text: DateFormat('dd/MM/yyyy').format(DateTime.now()));
final timeController =
TextEditingController(text: DateFormat('hh:mm a').format(DateTime.now()));
final notesController = TextEditingController();
final dealerController = TextEditingController();
final meetingSummaryController = TextEditingController();
final followUpActionsController = TextEditingController();
final nextVisitDateController = TextEditingController();
late TextEditingController retailerController = TextEditingController();
String selectedPurpose = 'Sales';
List<String> purposeOptions = ['Sales', 'Dues collection', 'Others'];
Future<void> _pickImage(ImageSource source) async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: source);
if (image != null) {
// Handle the picked image
// For example, you could update a state variable or send it to your provider
print('Image picked: ${image.path}');
// You might want to update your UI to show the selected image
setState(() {
notesController.text = image.path; // Just for demonstration
});
}
}
void _showImageSourceActionSheet() {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return SafeArea(
child: Wrap(
children: <Widget>[
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('Take a photo'),
onTap: () {
Navigator.pop(context);
_pickImage(ImageSource.camera);
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Choose from gallery'),
onTap: () {
Navigator.pop(context);
_pickImage(ImageSource.gallery);
},
),
],
),
);
},
);
}
Future<void> _selectNextVisitDate() async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) {
setState(() {
nextVisitDateController.text = DateFormat('dd/MM/yyyy').format(picked);
});
}
}
@override
void initState() {
_visitPdRdProvider = VisitPdRdProvider();
super.initState();
}
@override
Widget build(BuildContext context) {
retailerController = TextEditingController(text: widget.tradeName);
return ChangeNotifierProvider(
create: (context) => _visitPdRdProvider,
child: CommonBackground(
child: Stack(
children: [
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: Column(
children: [
const Text('Visit Retailers',
style: TextStyle(
fontSize: 20,
color: Colors.black,
fontWeight: FontWeight.w400,
fontFamily: 'Anek')),
Text(widget.tradeName ?? '',
style: const TextStyle(
fontSize: 20,
color: Colors.black,
fontWeight: FontWeight.w400,
fontFamily: 'Anek')),
],
),
backgroundColor: Colors.transparent,
elevation: 0,
),
drawer: const CommonDrawer(),
body: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(20.0)
.copyWith(top: 30, bottom: 30),
margin: const EdgeInsets.symmetric(horizontal: 16.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
color: const Color(0xffB4D1E5).withOpacity(0.9),
borderRadius: BorderRadius.circular(26.0)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CommonTextFormField(
readOnly: true,
title: 'Select Retailer',
fillColor: Colors.grey[300],
controller: retailerController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Visit date',
readOnly: true,
fillColor: Colors.grey[300],
controller: dateController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Time',
readOnly: true,
fillColor: Colors.grey[300],
controller: timeController),
const SizedBox(height: 15),
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Purpose of visit',
fillColor: Colors.white,
filled: true,
),
value: selectedPurpose,
items: purposeOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
setState(() {
selectedPurpose = newValue!;
});
},
),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Meeting summary:',
fillColor: Colors.white,
maxLines: 3,
controller: meetingSummaryController),
const SizedBox(height: 15),
CommonTextFormField(
title: 'Follow-up Actions:',
fillColor: Colors.white,
maxLines: 3,
controller: followUpActionsController),
const SizedBox(height: 15),
GestureDetector(
onTap: _selectNextVisitDate,
child: AbsorbPointer(
child: CommonTextFormField(
title: 'Next visit date:',
readOnly: true,
fillColor: Colors.white,
controller: nextVisitDateController,
),
),
),
const SizedBox(height: 15),
Row(
children: [
Expanded(
child: CommonTextFormField(
title: 'Attach Documents/Photos',
fillColor: Colors.white,
controller: notesController),
),
IconButton(
icon: const Icon(Icons.add_a_photo),
onPressed: _showImageSourceActionSheet,
),
],
),
const SizedBox(height: 15),
Consumer<VisitPdRdProvider>(
builder: (context, value, child) => Align(
alignment: Alignment.center,
child: CommonElevatedButton(
borderRadius: 30,
width: double.infinity,
height: kToolbarHeight - 10,
text: 'SUBMIT',
backgroundColor: const Color(0xff004791),
onPressed: () {
value.submitVisitPdRd(
pdRdId: widget.pdRdId!,
distributorType: widget.distributorType!,
tradeName: widget.tradeName!,
visitDate: dateController.text,
visitTime: timeController.text,
meetingSummary:
meetingSummaryController.text,
visitPurpose: selectedPurpose,
followUp: followUpActionsController.text,
nextVisitDate: nextVisitDateController.text,
);
},
),
),
),
],
),
),
],
),
),
),
Consumer<VisitPdRdProvider>(builder: (context, value, child) {
if (value.isLoading) {
return Container(
color: Colors.black.withOpacity(0.5),
child: const Center(
child: CircularProgressIndicator(),
),
);
}
return const SizedBox.shrink();
}),
],
),
),
);
}
}

View File

@ -1,5 +1,6 @@
class ApiUrls {
static const String baseUrl = 'https://cheminova-api-2.onrender.com/api/';
// static const String baseUrl = 'https://cheminova-api-2.onrender.com/api/';
static const String baseUrl = 'https://api.cnapp.co.in/api/';
static const String loginUrl = 'territorymanager/login';
static const String profileUrl = 'territorymanager/my-profile';
static const String markAttendanceUrl = 'v1/markattendance/territorymanager';
@ -14,10 +15,13 @@ class ApiUrls {
static const String getProducts = '${baseUrl}product/getAll/user/';
static const String getRd = 'inventory/distributors-TM/RetailDistributor';
static const String getPd = 'inventory/distributors-TM/PrincipalDistributor';
static const String submitProducts = 'inventory/add-TM';
static const String submitProducts = 'inventory/add';
static const String getSalesCoordinators = 'salescoordinator/getAll-TM';
static const String assignTask = 'task/assign-task';
static const String getProductsManual = 'productmanual/getall';
static const String getAllTasks = 'task/alltasks/';
static const String updateTaskInventoryUrl = '';
static const String salesTaskUrl = '${baseUrl}product/getAll/user/';
static const String postSalesTaskUrl = '${baseUrl}sales/add-TM';
static const String visitRdPd = '$baseUrl/visit';
static const String allTaskByDate = '${baseUrl}task/alltask';
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/services.dart';
class NoSpaceFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// Filter out spaces from the input
String newText = newValue.text.replaceAll(' ', '');
// Return the new formatted value without spaces
return TextEditingValue(
text: newText,
selection: newValue.selection,
);
}
}

View File

@ -0,0 +1,5 @@
extension StringExtension on String {
String capitalize() {
return "${this[0].toUpperCase()}${substring(1).toLowerCase()}";
}
}

View File

@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
class CommonTextFormField extends StatelessWidget {
final String title;
final TextEditingController? controller;
final TextCapitalization textCapitalization;
final String? Function(String?)? validator;
final Color? fillColor;
final bool? readOnly;
@ -28,6 +29,7 @@ class CommonTextFormField extends StatelessWidget {
this.inputFormatters,
this.maxLength,
this.onChanged,
this.textCapitalization = TextCapitalization.sentences,
this.obscureText = false,
});
@ -44,6 +46,7 @@ class CommonTextFormField extends StatelessWidget {
// fontFamily: 'Anek')),
TextFormField(
controller: controller,
textCapitalization: textCapitalization,
readOnly: readOnly ?? false,
maxLines: maxLines,
maxLength: maxLength,