Commit 7ba7402c authored by Sai Srinivas's avatar Sai Srinivas Committed by Sai Srinivas
Browse files

Edit form and some fixes added

parent d2939607
......@@ -428,7 +428,8 @@ class MyApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => CasualLeaveHistoryProvider()),
ChangeNotifierProvider(create: (_) => ContactProvider()),
ChangeNotifierProvider(create: (_) => QrProvider()),
ChangeNotifierProvider(create: (_) => CrmNearByGeneratorsProvider()),
ChangeNotifierProvider(create: (_) => EditCommonAccountProvider()),
],
child: Builder(
builder: (BuildContext context) {
......
......@@ -117,8 +117,18 @@ class _AccountslistState extends State<Accountslist> {
return (connection == "Online")
? Platform.isAndroid
? WillPopScope(
onWillPop: () => onBackPressed(context),
child: SafeArea(
onWillPop: () async {
onBackPressed(context);
_refreshList(context);
final provider = Provider.of<Accountslistprovider>(context, listen: false);
provider.resetValues();
provider.commonAccountListAPIFunction(context);
// Return true or false depending on whether you want to allow the pop
return true; // allow the back navigation
// return false; // prevent back navigation
},
child: SafeArea(
top: false,
bottom: true,
child: _scaffold(context),
......
......@@ -8,11 +8,13 @@ import 'package:generp/Notifiers/commonProvider/accountDetailsProvider.dart';
import 'package:generp/Utils/app_colors.dart';
import 'package:generp/Utils/commonServices.dart';
import 'package:generp/Utils/commonWidgets.dart';
import 'package:generp/screens/commom/editCommonAccount.dart';
import 'package:generp/screens/commom/transactionDetails.dart';
import 'package:generp/screens/finance/submitPaymentRequestionListsByMode.dart';
import 'package:provider/provider.dart';
import '../../Models/commonModels/commonAccountdetailsResponse.dart';
import 'addCommonPayment.dart';
class Accountslistdetails extends StatefulWidget {
final accountID;
......@@ -131,733 +133,762 @@ class _AccountslistdetailsState extends State<Accountslistdetails> {
),
resizeToAvoidBottomInset: true,
backgroundColor: AppColors.scaffold_bg_color,
body: SizedBox(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
body: Builder(builder: (context){
if (provider.isLoading) {
return const Center(
child: CircularProgressIndicator(color: Colors.blue),
);
}
return SizedBox(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30),
),
),
),
elevation: 2,
elevation: 2,
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(30),
),
),
// margin: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 10,
),
child: Column(
children: [
Row(
children: [
Expanded(
flex: 1,
child: SizedBox(
height: 50,
width: 35,
child: SvgPicture.asset(
"assets/svg/crm/lead_details_ic.svg",
),
),
),
SizedBox(width: 10),
Expanded(
flex: 5,
child: SizedBox(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
provider.accountDetails.name ?? "-",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.semi_black,
),
),
Text(
provider.accountDetails.type ?? "-",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_semi,
),
),
],
),
),
),
SizedBox(width: 10),
],
child: Column(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(30),
),
Visibility(
visible:
provider.showMoreDetails ? true : false,
child: Column(
),
// margin: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 10,
),
child: Column(
children: [
Row(
children: [
Container(
padding: EdgeInsets.symmetric(
vertical: 4,
Expanded(
flex: 1,
child: SizedBox(
height: 50,
width: 35,
child: SvgPicture.asset(
"assets/svg/crm/lead_details_ic.svg",
),
),
child: Row(
children: [
Expanded(
flex: 3,
child: Text(
"Bank Details",
),
SizedBox(width: 10),
Expanded(
flex: 5,
child: SizedBox(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
provider.accountDetails.name ?? "-",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
fontFamily: "JakartaSemiBold",
color: AppColors.semi_black,
),
),
),
Expanded(
flex: 6,
child: DottedLine(
dashGapLength: 4,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
Text(
provider.accountDetails.type ?? "-",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.grey_semi,
),
),
],
),
),
),
SizedBox(width: 2),
Expanded(
flex: 1,
child: InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => EditCommonAccountScreen(accountID: widget.accountID,))
).then((_) async {
var prov = Provider.of<Accountdetailsprovider>(context, listen: false);
await prov.accountdetailsAPIFunction(context, widget.accountID);
});
},
child: SizedBox(
height: 25,
width: 25,
child: SvgPicture.asset(
"assets/svg/crm_contact_edit.svg",
),
],
),
),
),
...List.generate(subHeadings1.length, (j) {
return Container(
],
),
Visibility(
visible:
provider.showMoreDetails ? true : false,
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
vertical: 7,
vertical: 4,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Text(
headings1[j],
"Bank Details",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
fontFamily: "JakartaSemiBold",
),
),
),
Expanded(
child: Text(
subHeadings1[j] == ""
? "-"
: subHeadings1[j],
style: TextStyle(
fontSize: 14,
color: Color(0xFF818181),
),
flex: 6,
child: DottedLine(
dashGapLength: 4,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
),
],
),
);
}),
Container(
padding: EdgeInsets.symmetric(
vertical: 4,
),
child: Row(
children: [
Expanded(
flex: 3,
child: Text(
"Address Details",
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaSemiBold",
),
),
...List.generate(subHeadings1.length, (j) {
return Container(
padding: EdgeInsets.symmetric(
vertical: 7,
),
Expanded(
flex: 6,
child: DottedLine(
dashGapLength: 4,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
headings1[j],
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
),
),
Expanded(
child: Text(
subHeadings1[j] == ""
? "-"
: subHeadings1[j],
style: TextStyle(
fontSize: 14,
color: Color(0xFF818181),
),
),
),
],
),
],
),
),
...List.generate(subHeadings2.length, (j) {
return Container(
);
}),
Container(
padding: EdgeInsets.symmetric(
vertical: 7,
vertical: 4,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Text(
headings2[j],
"Address Details",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
fontFamily: "JakartaSemiBold",
),
),
),
Expanded(
child: Text(
subHeadings2[j] == ""
? "-"
: subHeadings2[j],
style: TextStyle(
fontSize: 14,
color: Color(0xFF818181),
),
flex: 6,
child: DottedLine(
dashGapLength: 4,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
),
],
),
);
}),
Container(
padding: EdgeInsets.symmetric(
vertical: 4,
),
child: Row(
children: [
Expanded(
flex: 3,
child: Text(
"More Details",
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaSemiBold",
),
),
...List.generate(subHeadings2.length, (j) {
return Container(
padding: EdgeInsets.symmetric(
vertical: 7,
),
Expanded(
flex: 6,
child: DottedLine(
dashGapLength: 4,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
headings2[j],
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
),
),
Expanded(
child: Text(
subHeadings2[j] == ""
? "-"
: subHeadings2[j],
style: TextStyle(
fontSize: 14,
color: Color(0xFF818181),
),
),
),
],
),
],
),
),
...List.generate(subHeadings3.length, (j) {
return Container(
);
}),
Container(
padding: EdgeInsets.symmetric(
vertical: 7,
vertical: 4,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Text(
headings3[j],
"More Details",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
fontFamily: "JakartaSemiBold",
),
),
),
Expanded(
child: Text(
subHeadings3[j] == ""
? "-"
: subHeadings3[j],
style: TextStyle(
fontSize: 14,
color: Color(0xFF818181),
),
flex: 6,
child: DottedLine(
dashGapLength: 4,
dashGapColor: Colors.white,
dashColor: AppColors.grey_semi,
dashLength: 2,
lineThickness: 0.5,
),
),
],
),
);
}),
],
),
...List.generate(subHeadings3.length, (j) {
return Container(
padding: EdgeInsets.symmetric(
vertical: 7,
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
headings3[j],
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
color: AppColors.semi_black,
),
),
),
Expanded(
child: Text(
subHeadings3[j] == ""
? "-"
: subHeadings3[j],
style: TextStyle(
fontSize: 14,
color: Color(0xFF818181),
),
),
),
],
),
);
}),
],
),
),
),
InkResponse(
onTap: () async {
provider.showMoreDetails =
!provider.showMoreDetails;
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 5),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
provider.showMoreDetails
? "Hide Details"
: "View Details",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.app_blue,
InkResponse(
onTap: () async {
provider.showMoreDetails =
!provider.showMoreDetails;
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 5),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
provider.showMoreDetails
? "Hide Details"
: "View Details",
style: TextStyle(
fontFamily: "JakartaMedium",
fontSize: 14,
color: AppColors.app_blue,
),
),
),
Transform.flip(
flipY:
provider.showMoreDetails
? true
: false,
child: SvgPicture.asset(
"assets/svg/arrow_dropdown.svg",
height: 25,
width: 20,
color: AppColors.app_blue,
Transform.flip(
flipY:
provider.showMoreDetails
? true
: false,
child: SvgPicture.asset(
"assets/svg/arrow_dropdown.svg",
height: 25,
width: 20,
color: AppColors.app_blue,
),
),
),
],
],
),
),
),
),
],
],
),
),
),
],
],
),
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
margin: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
decoration: BoxDecoration(
color: Color(0xFFEDF7FF),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
provider.balanceDetails.balance ?? "-",
Container(
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
margin: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
decoration: BoxDecoration(
color: Color(0xFFEDF7FF),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
provider.balanceDetails.balance ?? "-",
style: TextStyle(
fontSize: 20,
fontFamily: "JakartaMedium",
color: AppColors.app_blue,
),
),
],
),
Container(
padding: EdgeInsets.only(top: 5),
child: Text(
"Total Balance",
maxLines: 1,
style: TextStyle(
fontSize: 20,
fontFamily: "JakartaMedium",
color: AppColors.app_blue,
fontSize: 14,
fontFamily: "JakartaRegular",
color: AppColors.semi_black,
),
),
],
),
Container(
padding: EdgeInsets.only(top: 5),
child: Text(
"Total Balance",
maxLines: 1,
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaRegular",
color: AppColors.semi_black,
),
),
),
],
],
),
),
),
Row(
children: [
...List.generate(2, (jj) {
final texts = ["Credited", "Debited"];
final heads = [
provider.balanceDetails.totalCredit ?? "-",
provider.balanceDetails.totalDebit ?? "-",
];
final svgs = [
"assets/svg/finance/cred_debit_ic.svg",
"assets/svg/finance/deb_credit_ic.svg",
];
return Expanded(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
margin: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
Row(
children: [
...List.generate(2, (jj) {
final texts = ["Credited", "Debited"];
final heads = [
provider.balanceDetails.totalCredit ?? "-",
provider.balanceDetails.totalDebit ?? "-",
];
final svgs = [
"assets/svg/finance/cred_debit_ic.svg",
"assets/svg/finance/deb_credit_ic.svg",
];
return Expanded(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
margin: EdgeInsets.symmetric(
horizontal: 10,
vertical: 15,
),
decoration: BoxDecoration(
color:
texts[jj] == "Credited"
? Color(0xFFFFEFEF)
: Color(0xFFE7FFE5),
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
heads[jj] ?? "-",
maxLines: 2,
style: TextStyle(
fontSize: 20,
color:
texts[jj] == "Credited"
? Color(0xFFEF3739)
: Color(0xFF0D9C00),
fontFamily: "JakartaMedium",
),
),
Container(
padding: EdgeInsets.only(top: 5),
child: Row(
children: [
Expanded(
flex: 3,
child: Text(
texts[jj] ?? "-",
maxLines: 1,
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaRegular",
color: AppColors.semi_black,
),
),
),
Expanded(
flex: 1,
child: SvgPicture.asset(svgs[jj]),
),
],
),
),
],
),
),
decoration: BoxDecoration(
color:
texts[jj] == "Credited"
? Color(0xFFFFEFEF)
: Color(0xFFE7FFE5),
borderRadius: BorderRadius.circular(16),
);
}),
],
),
],
),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: provider.ledgerList.length,
itemBuilder: (context, index) {
double runningBalance = 0;
int currentIndex = provider.ledgerList.indexOf(
provider.ledgerList[index],
);
for (var i = 0; i <= currentIndex; i++) {
var ledgerItem = provider.ledgerList[i];
double credit =
double.tryParse(
ledgerItem.creditAmount.toString(),
) ??
0;
double debit =
double.tryParse(
ledgerItem.debitAmount.toString(),
) ??
0;
runningBalance += (debit - credit);
}
return InkResponse(
onTap: () async {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => Transactiondetails(
paymentID: provider.ledgerList[index].refId,
type: provider.ledgerList[index].type,
description:
provider.ledgerList[index].description,
),
),
);
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
margin: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: SizedBox(
child:
provider.ledgerList[index].type ==
"Credit"
? SvgPicture.asset(
"assets/svg/finance/debited_filled_ic.svg",
height: 45,
width: 45,
fit: BoxFit.contain,
)
: SvgPicture.asset(
"assets/svg/finance/credited_filled_ic.svg",
height: 45,
width: 45,
fit: BoxFit.contain,
),
),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
heads[jj] ?? "-",
maxLines: 2,
style: TextStyle(
fontSize: 20,
color:
texts[jj] == "Credited"
? Color(0xFFEF3739)
: Color(0xFF0D9C00),
fontFamily: "JakartaMedium",
),
),
Container(
padding: EdgeInsets.only(top: 5),
child: Row(
),
SizedBox(width: 10),
Expanded(
flex: 8,
child: SizedBox(
child: Column(
children: [
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
flex: 5,
child: SizedBox(
child: Text(
provider
.ledgerList[index]
.description ??
"-",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
),
),
),
),
Spacer(),
Expanded(
flex: 3,
child: Text(
texts[jj] ?? "-",
maxLines: 1,
style: TextStyle(
fontSize: 14,
fontFamily: "JakartaRegular",
color: AppColors.semi_black,
child: SizedBox(
child: RichText(
maxLines: 1,
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
text: TextSpan(
children: [
TextSpan(
text:
provider
.ledgerList[index]
.type ==
"Credit"
? "-"
: "+",
style: TextStyle(
color:
provider
.ledgerList[index]
.type ==
"Credit"
? Color(
0xFFEF3739,
)
: Color(
0xFF0D9C00,
),
fontSize: 14,
fontFamily:
"JakartaRegular",
),
),
TextSpan(
text:
"₹${provider.ledgerList[index].type == "Credit" ? "${provider.ledgerList[index].creditAmount}" : "${provider.ledgerList[index].debitAmount}"}",
style: TextStyle(
color:
provider
.ledgerList[index]
.type ==
"Credit"
? Color(
0xFFEF3739,
)
: Color(
0xFF0D9C00,
),
fontSize: 14,
fontFamily:
"JakartaRegular",
),
),
],
),
),
),
),
],
),
SizedBox(height: 7),
Row(
children: [
Expanded(
flex: 1,
child: SvgPicture.asset(svgs[jj]),
flex: 5,
child: SizedBox(
child: Text(
provider
.ledgerList[index]
.date ??
"-",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "JakartaRegular",
color: AppColors.grey_semi,
fontSize: 12,
),
),
),
),
Spacer(),
// Expanded(
// flex: 3,
// child: SizedBox(
// child: RichText(
// maxLines: 1,
// textAlign: TextAlign.right,
// overflow: TextOverflow.ellipsis,
// text: TextSpan(
// children: [
// TextSpan(
// text: "Bal: ",
// style: TextStyle(
// color:
// AppColors
// .semi_black,
// fontSize: 12,
// fontFamily:
// "JakartaRegular",
// ),
// ),
// TextSpan(
// text: "$runningBalance",
// style: TextStyle(
// color:
// AppColors.grey_semi,
// fontSize: 12,
// fontFamily:
// "JakartaMedium",
// ),
// ),
// ],
// ),
// ),
// ),
// ),
],
),
),
],
],
),
),
),
);
}),
],
),
],
],
),
),
);
},
),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: provider.ledgerList.length,
itemBuilder: (context, index) {
double runningBalance = 0;
int currentIndex = provider.ledgerList.indexOf(
provider.ledgerList[index],
);
for (var i = 0; i <= currentIndex; i++) {
var ledgerItem = provider.ledgerList[i];
double credit =
double.tryParse(
ledgerItem.creditAmount.toString(),
) ??
0;
double debit =
double.tryParse(
ledgerItem.debitAmount.toString(),
) ??
0;
runningBalance += (debit - credit);
}
return InkResponse(
onTap: () async {
if ([
"Vendor",
"Customer",
].contains(provider.accountDetails.type)) ...[
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => Transactiondetails(
paymentID: provider.ledgerList[index].refId,
type: provider.ledgerList[index].type,
description:
provider.ledgerList[index].description,
),
(context) => Submitpaymentrequestionlistsbymode(
mode: "other",
pageTitleName: "Add Payment Request (Other)",
accountId: provider.accountDetails.id!,
),
),
);
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
alignment: Alignment.center,
height: 45,
margin: EdgeInsets.only(
left: 5.0,
right: 5.0,
top: 5.0,
bottom: 5.0,
),
margin: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
color: AppColors.app_blue, //1487C9
borderRadius: BorderRadius.circular(15.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: SizedBox(
child:
provider.ledgerList[index].type ==
"Credit"
? SvgPicture.asset(
"assets/svg/finance/debited_filled_ic.svg",
height: 45,
width: 45,
fit: BoxFit.contain,
)
: SvgPicture.asset(
"assets/svg/finance/credited_filled_ic.svg",
height: 45,
width: 45,
fit: BoxFit.contain,
),
),
),
SizedBox(width: 10),
Expanded(
flex: 8,
child: SizedBox(
child: Column(
children: [
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Expanded(
flex: 5,
child: SizedBox(
child: Text(
provider
.ledgerList[index]
.description ??
"-",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
),
),
),
),
Spacer(),
Expanded(
flex: 3,
child: SizedBox(
child: RichText(
maxLines: 1,
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
text: TextSpan(
children: [
TextSpan(
text:
provider
.ledgerList[index]
.type ==
"Credit"
? "-"
: "+",
style: TextStyle(
color:
provider
.ledgerList[index]
.type ==
"Credit"
? Color(
0xFFEF3739,
)
: Color(
0xFF0D9C00,
),
fontSize: 14,
fontFamily:
"JakartaRegular",
),
),
TextSpan(
text:
"₹${provider.ledgerList[index].type == "Credit" ? "${provider.ledgerList[index].creditAmount}" : "${provider.ledgerList[index].debitAmount}"}",
style: TextStyle(
color:
provider
.ledgerList[index]
.type ==
"Credit"
? Color(
0xFFEF3739,
)
: Color(
0xFF0D9C00,
),
fontSize: 14,
fontFamily:
"JakartaRegular",
),
),
],
),
),
),
),
],
),
SizedBox(height: 7),
Row(
children: [
Expanded(
flex: 5,
child: SizedBox(
child: Text(
provider
.ledgerList[index]
.date ??
"-",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: "JakartaRegular",
color: AppColors.grey_semi,
fontSize: 12,
),
),
),
),
Spacer(),
Expanded(
flex: 3,
child: SizedBox(
child: RichText(
maxLines: 1,
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
text: TextSpan(
children: [
TextSpan(
text: "Bal: ",
style: TextStyle(
color:
AppColors
.semi_black,
fontSize: 12,
fontFamily:
"JakartaRegular",
),
),
TextSpan(
text: "$runningBalance",
style: TextStyle(
color:
AppColors.grey_semi,
fontSize: 12,
fontFamily:
"JakartaMedium",
),
),
],
),
),
),
),
],
),
],
),
),
child: Center(
child: Text(
"Add Payment Request (Other)",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontFamily: "JakartaMedium",
fontSize: 15,
),
],
),
),
);
},
),
if ([
"Vendor",
"Customer",
].contains(provider.accountDetails.type)) ...[
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => Submitpaymentrequestionlistsbymode(
mode: "other",
pageTitleName: "Add Payment Request (Other)",
accountId: provider.accountDetails.id!,
),
),
);
},
child: Container(
alignment: Alignment.center,
height: 45,
margin: EdgeInsets.only(
left: 5.0,
right: 5.0,
top: 5.0,
bottom: 5.0,
),
decoration: BoxDecoration(
color: AppColors.app_blue, //1487C9
borderRadius: BorderRadius.circular(15.0),
),
child: Center(
child: Text(
"Add Payment Request (Other)",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontFamily: "JakartaMedium",
fontSize: 15,
),
),
),
),
),
],
],
],
),
),
),
),
);
}
)
);
},
);
......
......@@ -31,7 +31,7 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
Dropdowntheme ddtheme = Dropdowntheme();
int _currentStep = 0;
final _formKey = GlobalKey<FormState>();
List<FocusNode> focusNodes = List.generate(20, (index) => FocusNode());
final List<FocusNode> focusNodes = List.generate(30, (index) => FocusNode());
Map _source = {ConnectivityResult.mobile: true};
final MyConnectivity _connectivity = MyConnectivity.instance;
......@@ -274,7 +274,7 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
// custom continue logic:
if (_currentStep == 0) {
// validate step1 via provider
if (provider.validateStep1()) {
if (provider.validateStep1(context)) {
setState(() => _currentStep = 1);
} else {
// show error (provider sets errors and notifies)
......@@ -337,7 +337,7 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
setState(() => _currentStep = 0);
} else if (value == 1) {
// user wants to jump to step 1 - ensure step 0 valid
if (provider.validateStep1()) {
if (provider.validateStep1(context)) {
setState(() => _currentStep = 1);
} else {
provider.notifyListeners();
......@@ -345,7 +345,7 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
}
} else if (value == 2) {
// allow jump if step0 valid; step1 optional
if (provider.validateStep1()) {
if (provider.validateStep1(context)) {
setState(() => _currentStep = 2);
} else {
provider.notifyListeners();
......@@ -353,7 +353,7 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
}
} else if (value == 3) {
// final - require step0 valid
if (!provider.validateStep1()) {
if (!provider.validateStep1(context)) {
provider.notifyListeners();
setState(() => _currentStep = 0);
return;
......@@ -440,9 +440,12 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
"Company Name",
"Enter Company Name",
(p0) {
provider.updateName(p0);
provider.checkInputsAPI(context, "name", provider.nameController.text);
},
provider.updateName(p0);
provider.checkInputsAPI(context, "name", provider.nameController.text);
// Recompare with GST response if exists
provider.recheckNameWithGst();
},
TextInputType.text,
false,
null,
......@@ -708,52 +711,120 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// GST field - if provided, validate and autofill address; compare company name
textControllerWidget(
context,
provider.gstNumberController,
"GST Number",
"Enter GST Number",
(val) async {
// when user types, just update; validation on field submit
provider.gstNumberController.text = val;
provider.notifyListeners();
// GST field (unique focus node 9). Only one GST widget here.
Text("GST Number"),
const SizedBox(height: 6),
TextFormField(
controller: provider.gstNumberController,
focusNode: focusNodes[9],
textInputAction: TextInputAction.done,
keyboardType: TextInputType.text,
style: TextStyle(
height: 1.2,
color: Colors.black87,
backgroundColor: AppColors.text_field_color,
),
decoration: InputDecoration(
hintText: "Enter GST Number",
filled: true,
hintStyle: TextStyle(
height: 1.2,
backgroundColor: AppColors.text_field_color,
color: AppColors.grey_semi,
),
fillColor: AppColors.text_field_color,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
),
onChanged: (value) {
// only clear GST error when user edits
provider.updateGSTNumber(value);
final trimmed = value.trim();
if (trimmed.isEmpty) {
// user cleared GST -> clear response + errors
provider.gstResponse = null;
provider.gstNumberError = null;
provider.nameError = null; // also clear mismatch error, if any
provider.notifyListeners();
return;
}
// OPTIONAL: auto-validate once full 15-char GST typed
if (trimmed.length == 15) {
provider.checkAndApplyGst(context, trimmed);
}
},
onFieldSubmitted: (value) async {
final trimmed = value.trim();
if (trimmed.isNotEmpty) {
await provider.checkAndApplyGst(context, trimmed);
}
// move focus to Bank Account Number
FocusScope.of(context).requestFocus(focusNodes[10]);
},
TextInputType.text,
false,
null,
focusNodes[9],
focusNodes[10],
TextInputAction.next,
null,
// onEditingComplete / onFieldSubmitted, we will validate
),
// Validate GST when user leaves the field (on editing complete)
// To do that we use FocusNode
const SizedBox(height: 4),
errorWidget(context, provider.gstNumberError),
// Bank Account Number
const SizedBox(height: 4),
// errorWidget(context, provider.gstNumberError),
// Bank Account Number (use same style as other fields)
textControllerWidget(
context,
provider.bankAcNumberController,
"Bank Account Number",
"Enter Bank Account Number",
(p0) {
// update provider value and trigger bank validation if IFSC already present
provider.updateNumber(p0);
// attempt validation only when IFSC present
// If account entered but IFSC empty -> provider will set IFSC required error via checkAndApplyBank
if (provider.bankIfscCotroller.text.trim().isNotEmpty) {
_validateBankIfNeeded(provider);
// validate once IFSC is present
provider.checkAndApplyBank(context, p0.trim());
} else {
// If account present but IFSC empty, show IFSC required error
if (p0.trim().isNotEmpty) {
provider.bankIFSCError = "IFSC is required when account number is entered";
provider.notifyListeners();
} else {
provider.bankIFSCError = null;
provider.notifyListeners();
}
}
},
TextInputType.number,
false,
FilteringTextInputFormatter.digitsOnly,
focusNodes[10],
focusNodes[11],
focusNodes[10], // bank account unique
focusNodes[8], // move to IFSC next
TextInputAction.next,
),
const SizedBox(height: 4),
errorWidget(context, provider.bankAcNumberError),
// IFSC
// Bank IFSC
textControllerWidget(
context,
provider.bankIfscCotroller,
......@@ -761,28 +832,46 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
"Enter Bank IFSC",
(p0) {
provider.updateIFSC(p0);
// validate IFSC format locally; if good and account present, trigger server validation
if (_isIfscValidFormat(p0) && provider.bankAcNumberController.text.trim().isNotEmpty) {
_validateBankIfNeeded(provider);
} else {
if (p0.trim().isNotEmpty && !_isIfscValidFormat(p0)) {
provider.bankIFSCError = "Invalid IFSC format";
// local IFSC format check
final reg = RegExp(r'^[A-Za-z]{4}0[A-Za-z0-9]{6}$');
if (p0.trim().isEmpty) {
// if account exists and IFSC empty -> show required error
if (provider.bankAcNumberController.text.trim().isNotEmpty) {
provider.bankIFSCError = "IFSC is required when account number is entered";
provider.notifyListeners();
} else {
provider.bankIFSCError = null;
provider.notifyListeners();
}
return;
}
if (!reg.hasMatch(p0.trim())) {
provider.bankIFSCError = "Invalid IFSC format";
provider.notifyListeners();
return;
} else {
provider.bankIFSCError = null;
provider.notifyListeners();
}
// if acc and IFSC both present and valid -> call bank API
if (provider.bankAcNumberController.text.trim().isNotEmpty) {
provider.checkAndApplyBank(context, provider.bankAcNumberController.text.trim());
}
},
TextInputType.text,
false,
null,
focusNodes[8],
focusNodes[9],
focusNodes[8], // unique IFSC focus
focusNodes[11], // next focus after IFSC (holder / or UPI)
TextInputAction.next,
),
const SizedBox(height: 4),
errorWidget(context, provider.bankIFSCError),
// Bank Name (autofill)
const SizedBox(height: 8),
// Bank Name (autofill by API) (unique focus node 6)
textControllerWidget(
context,
provider.bankNameController,
......@@ -797,7 +886,10 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
TextInputAction.next,
),
errorWidget(context, provider.banknameError),
// Branch
const SizedBox(height: 8),
// Branch (unique focus node 7)
textControllerWidget(
context,
provider.branchNameController,
......@@ -808,11 +900,14 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
false,
null,
focusNodes[7],
focusNodes[8],
focusNodes[11],
TextInputAction.next,
),
errorWidget(context, provider.bankBranchError),
// Holder
const SizedBox(height: 8),
// Bank Holder Name (unique focus node 11) — previously incorrectly used 9
textControllerWidget(
context,
provider.bankHolderNameController,
......@@ -822,26 +917,30 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
TextInputType.text,
false,
null,
focusNodes[9],
focusNodes[10],
TextInputAction.next,
),
errorWidget(context, provider.bankHolderNameError),
// UPI
textControllerWidget(
context,
provider.bankUpiController,
"Bank UPI ID",
"Enter Bank UPI ID",
provider.updateUPI,
TextInputType.text,
false,
null,
focusNodes[11],
focusNodes[11], // <-- corrected unique index
focusNodes[12],
TextInputAction.next,
),
errorWidget(context, provider.upiError),
errorWidget(context, provider.bankHolderNameError),
const SizedBox(height: 8),
// Bank UPI (unique focus node 12)
// Bank UPI (use different focus nodes than Contact Person)
textControllerWidget(
context,
provider.bankUpiController,
"Bank UPI ID",
"Enter Bank UPI ID",
provider.updateUPI,
TextInputType.text,
false,
null,
focusNodes[17], //
focusNodes[18], // next focus
TextInputAction.next,
),
errorWidget(context, provider.upiError),
],
),
),
......@@ -973,6 +1072,23 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
return;
}
if (provider.validateStep1(context)) {
// move to first error step
final errStep = _firstErrorStep(provider);
if (errStep != null) {
setState(() {
_currentStep = errStep;
});
}else{
provider.submitClickced = true;
provider.notifyListeners();
await provider.submitCommonAccountsAPI(context, widget.from);
}
// ensure UI updated
provider.notifyListeners();
return;
}
// All good => submit
provider.submitClickced = true;
provider.notifyListeners();
......@@ -994,7 +1110,7 @@ class _AddcommonpaymentState extends State<Addcommonpayment> {
onTap: () {
setState(() {
if (_currentStep == 0) {
if (provider.validateStep1()) {
if (provider.validateStep1(context)) {
_currentStep = 1;
} else {
provider.notifyListeners();
......
// edit_common_account_screen.dart
import 'dart:async';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:generp/Notifiers/HomeScreenNotifier.dart';
import 'package:generp/Notifiers/commonProvider/editCommonAccountProvider.dart';
import 'package:generp/Utils/app_colors.dart';
import 'package:generp/Utils/commonWidgets.dart';
import 'package:generp/Utils/dropdownTheme.dart';
import '../../Models/commonModels/DistrictsResponse.dart';
import '../../Models/commonModels/SubLocationsResponse.dart';
import '../../Models/commonModels/commonAddAccountsViewResponse.dart';
class EditCommonAccountScreen extends StatefulWidget {
final String accountID;
const EditCommonAccountScreen({super.key, required this.accountID});
@override
State<EditCommonAccountScreen> createState() => _EditCommonAccountScreenState();
}
class _EditCommonAccountScreenState extends State<EditCommonAccountScreen> {
Timer? _nameDebounce;
Timer? _gstDebounce;
final _formKey = GlobalKey<FormState>();
// Focus nodes
late List<FocusNode> focusNodes;
static const int FN_ACCOUNT = 0;
static const int FN_NAME = 1;
static const int FN_NAME_NEXT = 2;
static const int FN_MOBILE = 3;
static const int FN_CONTACT = 4;
static const int FN_GST = 5;
static const int FN_STATE = 6;
static const int FN_DISTRICT = 7;
static const int FN_SUBLOC = 8;
static const int FN_ADDRESS = 9;
static const int FN_BANK_ACC = 10;
static const int FN_IFSC = 11;
static const int FN_BANK_NAME = 12;
static const int FN_BANK_BRANCH = 13;
static const int FN_BANK_HOLDER = 14;
static const int FN_UPI = 15;
static const int FN_CONTACT_DESIG = 16;
static const int FN_CONTACT_ALTMOB = 17;
static const int FN_CONTACT_TELE = 18;
static const int FN_CONTACT_MAIL = 19;
int _currentStep = 0;
final Dropdowntheme ddtheme = Dropdowntheme();
@override
void initState() {
super.initState();
// create enough focus nodes (0..19)
focusNodes = List.generate(20, (_) => FocusNode());
WidgetsBinding.instance.addPostFrameCallback((_) {
final prov = Provider.of<EditCommonAccountProvider>(context, listen: false);
// ensure only once: reset then load
prov.resetValues();
prov.loadAccountDetailsForEdit(context, widget.accountID);
});
}
@override
void dispose() {
for (final fn in focusNodes) {
try {
fn.dispose();
} catch (_) {}
}
_nameDebounce?.cancel();
_gstDebounce?.cancel();
// reset provider values when leaving screen to avoid stale state
final prov = Provider.of<EditCommonAccountProvider>(context, listen: false);
prov.resetValues();
super.dispose();
}
// small helper to check IFSC simple pattern
bool _isIfscValidFormat(String ifsc) {
final reg = RegExp(r'^[A-Za-z]{4}0[A-Za-z0-9]{6}$');
return reg.hasMatch(ifsc.trim());
}
bool _isGstValidFormat(String gst) {
gst = gst.trim().toUpperCase();
final reg = RegExp(r'^[0-3][0-9][A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$');
return reg.hasMatch(gst);
}
Future<void> _validateGstIfNeeded(EditCommonAccountProvider provider) async {
final gst = provider.gstNumberController.text.trim();
if (!_isGstValidFormat(gst)) {
provider.gstNumberError = "Invalid GST format";
provider.notifyListeners();
return;
}
if (gst.isEmpty) return;
final homeProv = Provider.of<HomescreenNotifier>(context, listen: false);
provider.isLoading = true;
await provider.validateGstNumber(homeProv.empId, homeProv.session, gst);
provider.isLoading = false;
}
Future<void> _validateBankIfNeeded(EditCommonAccountProvider provider) async {
final acc = provider.bankAcNumberController.text.trim();
final ifsc = provider.bankIfscCotroller.text.trim();
final accList = provider.detailsListResponse?.accountList!.first;
if (acc.isEmpty && ifsc.isEmpty) return;
if (acc.isNotEmpty && ifsc.isEmpty) {
provider.bankIFSCError = "IFSC is required when account number is entered";
provider.notifyListeners();
return;
}
if (acc.isEmpty && ifsc.isEmpty) {
provider.bankNameController.text = accList?.bankName ?? "";
provider.branchNameController.text = accList?.bankBranchName ?? "";
provider.bankHolderNameController.text = accList?.bankAccountHolderName ?? "";
provider.notifyListeners();
return;
}
if (!_isIfscValidFormat(ifsc)) {
provider.bankIFSCError = "Invalid IFSC format";
provider.notifyListeners();
return;
}
final homeProv = Provider.of<HomescreenNotifier>(context, listen: false);
provider.isLoading = true;
await provider.validateBankDetails(homeProv.empId, homeProv.session, acc);
provider.isLoading = false;
// If validateBankDetails succeeded provider.bankResponse will be set and UI autofills
if (provider.bankResponse != null && provider.bankResponse!.error == "0") {
provider.bankNameController.text = provider.bankResponse!.bankName ?? provider.bankNameController.text;
provider.branchNameController.text = provider.bankResponse!.branch ?? provider.branchNameController.text;
provider.bankHolderNameController.text = provider.bankResponse!.nameAtBank ?? provider.bankHolderNameController.text;
provider.notifyListeners();
}
}
int? _firstErrorStep(EditCommonAccountProvider p) {
if ((p.accountError?.isNotEmpty ?? false) ||
(p.nameError?.isNotEmpty ?? false) ||
(p.mobileError?.isNotEmpty ?? false) ||
(p.contactPersonError?.isNotEmpty ?? false)) {
return 0;
}
if ((p.stateError?.isNotEmpty ?? false) ||
(p.districtError?.isNotEmpty ?? false) ||
(p.localityError?.isNotEmpty ?? false) ||
(p.addressError?.isNotEmpty ?? false)) {
return 1;
}
if ((p.gstNumberError?.isNotEmpty ?? false) ||
(p.bankAcNumberError?.isNotEmpty ?? false) ||
(p.bankIFSCError?.isNotEmpty ?? false) ||
(p.banknameError?.isNotEmpty ?? false) ||
(p.bankBranchError?.isNotEmpty ?? false) ||
(p.bankHolderNameError?.isNotEmpty ?? false) ||
(p.upiError?.isNotEmpty ?? false)) {
return 2;
}
if ((p.desigantionError?.isNotEmpty ?? false) ||
(p.altMobError?.isNotEmpty ?? false) ||
(p.teleError?.isNotEmpty ?? false) ||
(p.mailError?.isNotEmpty ?? false)) {
return 3;
}
return null;
}
@override
Widget build(BuildContext context) {
return Consumer<EditCommonAccountProvider>(builder: (context, provider, child) {
return Scaffold(
appBar: appbar2New(context, "Edit Account", provider.resetValues, const SizedBox.shrink(), 0xFFFFFFFF),
backgroundColor: AppColors.scaffold_bg_color,
body: Form(
key: _formKey,
child: Stepper(
type: StepperType.horizontal,
currentStep: _currentStep,
onStepContinue: () async {
// custom continue logic same as your flow
if (_currentStep == 0) {
provider.validateStep();
if (provider.gstNumberController.text.trim().isNotEmpty) {
await _validateGstIfNeeded(provider);
}
if (provider.validateStep1(context)) {
setState(() => _currentStep = 1);
} else {
provider.notifyListeners();
setState(() => _currentStep = 0);
}
} else if (_currentStep == 1) {
// optional step -> only validate if something filled
if (provider.stateSearchController.text.trim().isNotEmpty ||
provider.districtSearchController.text.trim().isNotEmpty ||
provider.addressController.text.trim().isNotEmpty) {
if (provider.validateStep1(context)) {
setState(() => _currentStep = 2);
} else {
provider.notifyListeners();
}
} else {
setState(() => _currentStep = 2);
}
} else if (_currentStep == 2) {
// bank/gst step: if fields present, validate
if (provider.gstNumberController.text.trim().isNotEmpty) {
await _validateGstIfNeeded(provider);
}
provider.validateStep3();
if (provider.bankAcNumberController.text.trim().isNotEmpty || provider.bankIfscCotroller.text.trim().isNotEmpty) {
await _validateBankIfNeeded(provider);
}
// if any errors remain, stay
final hasBankErrors = (provider.gstNumberError?.isNotEmpty ?? false) ||
(provider.bankAcNumberError?.isNotEmpty ?? false) ||
(provider.bankIFSCError?.isNotEmpty ?? false);
if (hasBankErrors) {
provider.notifyListeners();
setState(() => _currentStep = 2);
} else {
setState(() => _currentStep = 3);
}
} else {
// last step - do nothing (submit handled by controlsBuilder)
}
},
onStepCancel: () {
if (_currentStep > 0) setState(() => _currentStep -= 1);
},
onStepTapped: (i) async {
// allow step tapping but guard jump if step0 invalid
if (i == 0) {
setState(() => _currentStep = 0);
} else if (i == 1) {
if (provider.validateStep1(context)) {
setState(() => _currentStep = 1);
} else {
provider.notifyListeners();
setState(() => _currentStep = 0);
}
} else if (i == 2) {
if (provider.validateStep1(context)) {
setState(() => _currentStep = 2);
} else {
provider.notifyListeners();
setState(() => _currentStep = 0);
}
} else if (i == 3) {
if (!provider.validateStep1(context)) {
provider.notifyListeners();
setState(() => _currentStep = 0);
return;
}
setState(() => _currentStep = 3);
}
},
connectorColor: WidgetStatePropertyAll(AppColors.app_blue),
stepIconBuilder: (stepIndex, stepState) {
return CircleAvatar(
radius: 12,
backgroundColor: stepIndex <= _currentStep ? Colors.transparent : Colors.grey[300],
);
},
steps: [
// ---------------- Step 1 ----------------
Step(
title: const Text(''),
label: const Text("Step 1", style: TextStyle(fontSize: 12)),
content: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Account", style: TextStyle(color: AppColors.app_blue, fontSize: 16)),
const SizedBox(height: 8),
// Account dropdown (keeps DropdownButton2 style)
errorWidget(context, provider.accountError),
const SizedBox(height: 10),
// Company Name (debounced uniqueness check)
textControllerWidget(
context,
provider.nameController,
"Company Name",
"Enter Company Name",
(p0) {
// local update + recheck with GST
provider.updateName(p0);
provider.recheckNameWithGst();
// debounce uniqueness API call
_nameDebounce?.cancel();
_nameDebounce = Timer(const Duration(milliseconds: 600), () {
final val = provider.nameController.text.trim();
if (val.isNotEmpty) provider.checkInputsAPI(context, "name", val);
});
},
TextInputType.text,
false,
null,
focusNodes[FN_NAME],
focusNodes[FN_NAME_NEXT],
TextInputAction.next,
),
errorWidget(context, provider.nameError),
const SizedBox(height: 8),
// Mobile (debounced uniqueness check)
textControllerWidget(
context,
provider.mobileController,
"Mobile Number",
"Enter Mobile",
(p0) {
provider.updateMobile(p0);
// debounce mobile check
_nameDebounce?.cancel();
_nameDebounce = Timer(const Duration(milliseconds: 600), () {
final val = provider.mobileController.text.trim();
if (val.isNotEmpty) provider.checkInputsAPI(context, "mob1", val);
});
},
TextInputType.phone,
false,
FilteringTextInputFormatter.digitsOnly,
focusNodes[FN_MOBILE],
focusNodes[FN_CONTACT],
TextInputAction.next,
10,
),
errorWidget(context, provider.mobileError),
const SizedBox(height: 8),
// Contact Person - explicit TextFormField to avoid helper-side reassignment
Text("Contact Person Name"),
const SizedBox(height: 6),
TextFormField(
controller: provider.contactPersonController,
focusNode: focusNodes[FN_CONTACT],
textInputAction: TextInputAction.next,
keyboardType: TextInputType.text,
onChanged: (v) {
// only update provider state (provider may call notifyListeners inside)
provider.updateContactPerson(v);
// do not call heavy APIs here
},
decoration: InputDecoration(
hintText: "Enter Contact Person Name",
hintStyle: TextStyle(
height: 1.2,
backgroundColor: AppColors.text_field_color,
color: AppColors.grey_semi,
),
filled: true,
fillColor: AppColors.text_field_color,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
),
),
errorWidget(context, provider.contactPersonError),
const SizedBox(height: 12),
// GST (debounced validation + autofill). Typing + deletion will work now.
Text("GST Number"),
const SizedBox(height: 6),
TextFormField(
controller: provider.gstNumberController,
focusNode: focusNodes[FN_GST],
textInputAction: TextInputAction.done,
keyboardType: TextInputType.text,
onChanged: (value) {
provider.updateGSTNumber(value);
// if cleared -> reset responses immediately (keeps UI clean)
if (value.trim().isEmpty) {
provider.gstResponse = null;
provider.gstNumberError = null;
provider.nameError = null;
provider.notifyListeners();
return;
}
// immediate apply when full GST length typed (fast feedback)
if (value.trim().length == 15) {
_gstDebounce?.cancel();
provider.checkAndApplyGst(context, value.trim());
return;
}
// otherwise debounce (user typing)
_gstDebounce?.cancel();
_gstDebounce = Timer(const Duration(milliseconds: 700), () {
final trimmed = provider.gstNumberController.text.trim();
if (trimmed.isNotEmpty && trimmed.length == 15) {
provider.checkAndApplyGst(context, trimmed);
}
});
},
onFieldSubmitted: (value) async {
final trimmed = value.trim();
if (trimmed.isNotEmpty) await provider.checkAndApplyGst(context, trimmed);
FocusScope.of(context).requestFocus(focusNodes[FN_BANK_ACC]);
},
decoration: InputDecoration(
hintText: "Enter GST Number",
hintStyle: TextStyle(
height: 1.2,
backgroundColor: AppColors.text_field_color,
color: AppColors.grey_semi,
),
filled: true,
fillColor: AppColors.text_field_color,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
),
),
const SizedBox(height: 4),
errorWidget(context, provider.gstNumberError),
],
),
),
),
// ---------------- Step 2 ----------------
Step(
title: const Text(''),
label: const Text("Step 2", style: TextStyle(fontSize: 12)),
isActive: _currentStep >= 1,
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(bottom: 10),
child: Text(
"Address Details",
style: TextStyle(color: AppColors.app_blue, fontSize: 16, fontFamily: "JakartaMedium"),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// State
Text("State"),
const SizedBox(height: 6),
DropdownButtonHideUnderline(
child: Row(
children: [
Expanded(
child: DropdownButton2<States>(
focusNode: focusNodes[FN_STATE],
isExpanded: true,
hint: const Text('Select State', style: TextStyle(fontSize: 14)),
items: provider.states
.map((states) => DropdownMenuItem<States>(
value: states,
child: Text(states.name ?? '', style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis),
))
.toList(),
value: provider.states.contains(provider.selectedState) ? provider.selectedState : null,
onChanged: (States? value) {
if (value != null) {
provider.selectedState = value;
// Clear dependent district/sub-location
provider.districts.clear();
provider.subLocations.clear();
provider.getDistrictAPI(context, provider.selectedStateID);
}
},
dropdownSearchData: DropdownSearchData(
searchInnerWidgetHeight: 50,
searchController: provider.stateSearchController,
searchInnerWidget: Padding(
padding: const EdgeInsets.all(8),
child: TextFormField(
controller: provider.stateSearchController,
decoration: InputDecoration(isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), hintText: 'Search States...', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8))),
),
),
searchMatchFn: (item, searchValue) {
return item.value?.name?.toLowerCase().contains(searchValue.toLowerCase()) ?? false;
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) provider.stateSearchController.clear();
},
buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
],
),
),
errorWidget(context, provider.stateError),
const SizedBox(height: 12),
// District
Text("District"),
const SizedBox(height: 6),
DropdownButtonHideUnderline(
child: Row(
children: [
Expanded(
child: DropdownButton2<Districts>(
focusNode: focusNodes[FN_DISTRICT],
isExpanded: true,
hint: const Text('Select District', style: TextStyle(fontSize: 14)),
items: provider.districts
.map((dist) => DropdownMenuItem<Districts>(
value: dist,
child: Text(dist.district ?? '', style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis),
))
.toList(),
value: provider.districts.contains(provider.selectedDistricts) ? provider.selectedDistricts : null,
onChanged: (Districts? value) {
if (value != null) {
provider.selectedDistricts = value;
provider.getSubLocationAPI(context, provider.selectedDistrictId);
}
},
dropdownSearchData: DropdownSearchData(
searchInnerWidgetHeight: 50,
searchController: provider.districtSearchController,
searchInnerWidget: Padding(
padding: const EdgeInsets.all(8),
child: TextFormField(
controller: provider.districtSearchController,
decoration: InputDecoration(isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), hintText: 'Search Districts...', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8))),
),
),
searchMatchFn: (item, searchValue) {
return item.value?.district?.toLowerCase().contains(searchValue.toLowerCase()) ?? false;
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) provider.districtSearchController.clear();
},
buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
],
),
),
errorWidget(context, provider.districtError),
const SizedBox(height: 12),
// Sub Locality
Text("Sub Locality"),
const SizedBox(height: 6),
DropdownButtonHideUnderline(
child: Row(
children: [
Expanded(
child: DropdownButton2<SubLocations>(
focusNode: focusNodes[FN_SUBLOC],
isExpanded: true,
hint: const Text('Select Sub Locality', style: TextStyle(fontSize: 14)),
items: provider.subLocations
.map((subloc) => DropdownMenuItem<SubLocations>(
value: subloc,
child: Text(subloc.subLocality ?? '', style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis),
))
.toList(),
value: provider.subLocations.contains(provider.selectedSubLocations) ? provider.selectedSubLocations : null,
onChanged: (SubLocations? value) {
if (value != null) {
provider.selectedSubLocations = value;
}
},
dropdownSearchData: DropdownSearchData(
searchInnerWidgetHeight: 50,
searchController: provider.subLocSearchController,
searchInnerWidget: Padding(
padding: const EdgeInsets.all(8),
child: TextFormField(
controller: provider.subLocSearchController,
decoration: InputDecoration(isDense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), hintText: 'Search Sub Locality...', border: OutlineInputBorder(borderRadius: BorderRadius.circular(8))),
),
),
searchMatchFn: (item, searchValue) {
return item.value?.subLocality?.toLowerCase().contains(searchValue.toLowerCase()) ?? false;
},
),
onMenuStateChange: (isOpen) {
if (!isOpen) provider.subLocSearchController.clear();
},
buttonStyleData: ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData: ddtheme.dropdownStyleData,
),
),
],
),
),
errorWidget(context, provider.localityError),
const SizedBox(height: 12),
// Address text field (same UI as your other address field)
textControllerWidget(
context,
provider.addressController,
"Address",
"Enter Address",
provider.updateAddress,
TextInputType.text,
false,
null,
focusNodes[FN_ADDRESS],
focusNodes[FN_BANK_ACC],
TextInputAction.next,
),
errorWidget(context, provider.addressError),
],
),
),
],
),
),
// ---------------- Step 3 ----------------
Step(
title: const Text(''),
label: const Text("Step 3", style: TextStyle(fontSize: 12)),
content: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Bank Details", style: TextStyle(color: AppColors.app_blue, fontSize: 16)),
const SizedBox(height: 8),
const SizedBox(height: 12),
// Bank Account Number
textControllerWidget(
context,
provider.bankAcNumberController,
"Bank Account Number",
"Enter Bank Account Number",
(p0) {
provider.updateNumber(p0);
// IFSC required if account entered
if (p0.trim().isNotEmpty && provider.bankIfscCotroller.text.trim().isEmpty) {
provider.bankIFSCError = "IFSC is required when account number is entered";
provider.notifyListeners();
} else if (p0.trim().isNotEmpty && provider.bankIfscCotroller.text.trim().isNotEmpty) {
// both present -> validate bank
provider.checkAndApplyBank(context, p0.trim());
} else {
// cleared -> reset bank response/errors
if (p0.trim().isEmpty) {
provider.bankResponse = null;
provider.bankAcNumberError = null;
provider.bankIFSCError = null;
provider.notifyListeners();
}
}
},
TextInputType.number,
false,
FilteringTextInputFormatter.digitsOnly,
focusNodes[FN_BANK_ACC],
focusNodes[FN_IFSC],
TextInputAction.next,
),
errorWidget(context, provider.bankAcNumberError),
// IFSC
textControllerWidget(
context,
provider.bankIfscCotroller,
"Bank IFSC",
"Enter Bank IFSC",
(p0) async {
provider.updateIFSC(p0);
if (p0.trim().isEmpty) {
if (provider.bankAcNumberController.text.trim().isNotEmpty) {
provider.bankIFSCError = "IFSC is required when account number is entered";
provider.notifyListeners();
} else {
provider.bankIFSCError = null;
provider.notifyListeners();
}
return;
}
if (!_isIfscValidFormat(p0)) {
provider.bankIFSCError = "Invalid IFSC format";
provider.notifyListeners();
return;
} else {
provider.bankIFSCError = null;
provider.notifyListeners();
}
// if both present -> validate
if (provider.bankAcNumberController.text.trim().isNotEmpty) {
await provider.checkAndApplyBank(context, provider.bankAcNumberController.text.trim());
}
},
TextInputType.text,
false,
null,
focusNodes[FN_IFSC],
focusNodes[FN_BANK_NAME],
TextInputAction.next,
),
errorWidget(context, provider.bankIFSCError),
const SizedBox(height: 8),
// Bank Name (autofilled, readonly)
Text("Bank Name"),
const SizedBox(height: 6),
TextFormField(
controller: provider.bankNameController,
focusNode: focusNodes[FN_BANK_NAME],
readOnly: true,
decoration: InputDecoration(
hintText: "Bank Name",
hintStyle: TextStyle(
height: 1.2,
backgroundColor: AppColors.text_field_color,
color: AppColors.grey_semi,
),
filled: true,
fillColor: AppColors.text_field_color,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
),
),
errorWidget(context, provider.banknameError),
const SizedBox(height: 8),
// Branch (read-only)
Text("Bank Branch"),
const SizedBox(height: 6),
TextFormField(
controller: provider.branchNameController,
focusNode: focusNodes[FN_BANK_BRANCH],
readOnly: true,
decoration: InputDecoration(
hintText: "Branch ",
hintStyle: TextStyle(
height: 1.2,
backgroundColor: AppColors.text_field_color,
color: AppColors.grey_semi,
),
filled: true,
fillColor: AppColors.text_field_color,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
),
),
errorWidget(context, provider.bankBranchError),
const SizedBox(height: 8),
// Bank Holder (read-only)
Text("Bank Holder Name"),
const SizedBox(height: 6),
TextFormField(
controller: provider.bankHolderNameController,
focusNode: focusNodes[FN_BANK_HOLDER],
readOnly: true,
decoration: InputDecoration(
hintText: "Holder ",
hintStyle: TextStyle(
height: 1.2,
backgroundColor: AppColors.text_field_color,
color: AppColors.grey_semi,
),
filled: true,
fillColor: AppColors.text_field_color,
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none),
),
),
errorWidget(context, provider.bankHolderNameError),
const SizedBox(height: 8),
// UPI (editable)
textControllerWidget(
context,
provider.bankUpiController,
"Bank UPI ID",
"Enter Bank UPI ID",
provider.updateUPI,
TextInputType.text,
false,
null,
focusNodes[FN_UPI],
focusNodes[FN_CONTACT_MAIL],
TextInputAction.next,
),
errorWidget(context, provider.upiError),
],
),
),
),
// ---------------- Step 4 ----------------
Step(
title: const Text(''),
label: const Text("Step 4", style: TextStyle(fontSize: 12)),
content: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Contact Details", style: TextStyle(color: AppColors.app_blue, fontSize: 16)),
const SizedBox(height: 8),
textControllerWidget(
context,
provider.contectPersonMailController,
"Mail ID",
"Enter Customer Mail ID",
provider.updateMail,
TextInputType.emailAddress,
false,
null,
focusNodes[FN_CONTACT_MAIL],
FocusNode(),
TextInputAction.done,
),
errorWidget(context, provider.mailError),
],
),
),
),
],
controlsBuilder: (context, details) {
return Column(
children: [
if (_currentStep == 3) ...[
InkResponse(
onTap: provider.isSubmitting
? null
: () async {
// trigger validations for gst/bank if present
if (provider.gstNumberController.text.trim().isNotEmpty) {
await _validateGstIfNeeded(provider);
}
if (provider.bankAcNumberController.text.trim().isNotEmpty && provider.bankIfscCotroller.text.trim().isNotEmpty) {
await _validateBankIfNeeded(provider);
}
final ok = provider.validatereceiptForm(context);
if (!ok) {
final err = _firstErrorStep(provider);
if (err != null) setState(() => _currentStep = err);
provider.notifyListeners();
return;
}
// call update API
provider.isLoading = true;
final success = await provider.updateAccountDetailsAPIFunction(
context,
accId: widget.accountID,
name: provider.nameController.text.trim(),
mob1: provider.mobileController.text.trim(),
email: provider.contectPersonMailController.text.trim(),
address: provider.addressController.text.trim(),
state: provider.selectedStateID,
district: provider.selectedDistrictId,
subLocality: provider.selectedSubLocID,
bankName: provider.bankNameController.text.trim(),
branchName: provider.branchNameController.text.trim(),
bankIfscCode: provider.bankIfscCotroller.text.trim(),
accHolderName: provider.bankHolderNameController.text.trim(),
bankAccNumber: provider.bankAcNumberController.text.trim(),
bankUpiId: provider.bankUpiController.text.trim(),
);
provider.isLoading = false;
if (success) {
Navigator.pop(context, true); // or pass updated id as you wish
} else {
if (kDebugMode) debugPrint("update failed: ${provider.updateErrorMessage}");
}
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(color: AppColors.app_blue, borderRadius: BorderRadius.circular(15)),
child: provider.submitClickced
? CircularProgressIndicator.adaptive(valueColor: AlwaysStoppedAnimation(AppColors.white))
: Text("Submit", style: TextStyle(fontSize: 15, fontFamily: "JakartaMedium", color: Colors.white)),
),
),
] else ...[
InkResponse(
onTap: () {
final error = provider.gstNumberError ?? "";
// proceed to next step (uses Stepper.onStepContinue logic)
if (error.isEmpty) {
details.onStepContinue!();
}
},
child: Container(
height: 45,
alignment: Alignment.center,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(color: AppColors.app_blue, borderRadius: BorderRadius.circular(15)),
child: Text("Proceed to Next Step", textAlign: TextAlign.start, style: TextStyle(fontSize: 15, fontFamily: "JakartaMedium", color: Colors.white)),
),
),
],
if (_currentStep > 0) TextButton(onPressed: details.onStepCancel, child: Text('Back', style: TextStyle(color: AppColors.app_blue)))
],
);
},
),
),
);
});
}
}
import 'dart:io';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/Utils/app_colors.dart';
import 'package:generp/Utils/commonWidgets.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:interactive_slider/interactive_slider.dart';
import 'package:provider/provider.dart';
import '../../Notifiers/crmProvider/CrmNearByGeneratorsProvider.dart';
import '../../Utils/dropdownTheme.dart';
class CrmNearbyGenerators extends StatefulWidget {
const CrmNearbyGenerators({super.key});
@override
State<CrmNearbyGenerators> createState() => _CrmNearbyGeneratorsState();
}
class _CrmNearbyGeneratorsState extends State<CrmNearbyGenerators> {
Dropdowntheme ddtheme = Dropdowntheme();
@override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
var provider = Provider.of<CrmNearByGeneratorsProvider>(
context,
listen: false,
);
provider.getLocationPermission(context);
});
}
@override
Widget build(BuildContext context) {
debugPrint("Nearbygenerators widget rebuilt");
return Consumer<CrmNearByGeneratorsProvider>(
builder: (context, provider, child) {
var sendWidget = GestureDetector(
onTap: () {
_showFilterBottomSheet(context);
},
child: SvgPicture.asset("assets/svg/filter_ic.svg", height: 25),
);
return WillPopScope(
onWillPop: () => onBackPressed(context),
child: SafeArea(
top: false,
bottom: Platform.isIOS ? false : true,
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: appbar2(
context,
"Nearby Generators",
provider.resetAll,
sendWidget,
),
backgroundColor: AppColors.scaffold_bg_color,
body: Container(
child: SingleChildScrollView(
child: Column(
children: [
ClipRRect(
// Apply border radius using ClipRRect
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30.0),
topRight: Radius.circular(30.0),
),
// padding: EdgeInsets.fromLTRB(10, 20, 10, 20),
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: Stack(
children: [
GoogleMap(
scrollGesturesEnabled: true,
rotateGesturesEnabled: true,
myLocationEnabled: true,
zoomGesturesEnabled: true,
zoomControlsEnabled: true,
gestureRecognizers: {
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
Factory<PanGestureRecognizer>(
() => PanGestureRecognizer(),
),
Factory<ScaleGestureRecognizer>(
() => ScaleGestureRecognizer(),
), // Prioritize pinch-to-zoom
},
initialCameraPosition: CameraPosition(
target: provider.startLocation,
zoom: 14.0,
),
markers: provider.markers.toSet(),
mapType: MapType.normal,
onMapCreated: (controller) {
setState(() {
provider.mapController = controller;
});
},
onCameraMove: (position) {
provider.onCameraMove(context, position);
},
),
],
),
),
),
],
),
),
),
),
),
);
},
);
}
Future<void> _showFilterBottomSheet(BuildContext context) {
return showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
enableDrag: true,
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return SafeArea(
child: Consumer<CrmNearByGeneratorsProvider>(
builder: (context, provider, child) {
return Container(
margin: EdgeInsets.only(
bottom: 15,
left: 15,
right: 15,
top: 15,
),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Filter",
style: TextStyle(
color: AppColors.app_blue,
fontFamily: "JakartaSemiBold",
fontSize: 16,
),
),
SizedBox(height: 15),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Status',
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w500,
),
),
),
DropdownButtonHideUnderline(
child: Row(
children: [
Expanded(
child: DropdownButton2<String>(
isExpanded: true,
hint: const Row(
children: [
Expanded(
child: Text(
'Select Complaint Status',
style: TextStyle(fontSize: 14),
overflow: TextOverflow.ellipsis,
),
),
],
),
items:
<String>[
'Active',
'Inactive',
'Suspense',
]
.map(
(
value,
) => DropdownMenuItem<String>(
value: value,
child: Text(
value ?? '',
style: const TextStyle(
fontSize: 14,
),
overflow:
TextOverflow.ellipsis,
),
),
)
.toList(),
value: provider.selectedItem,
onChanged: (String? newValue) {
setState(() {
provider.selectedItem = newValue!;
});
},
buttonStyleData:
ddtheme.buttonStyleData,
iconStyleData: ddtheme.iconStyleData,
menuItemStyleData:
ddtheme.menuItemStyleData,
dropdownStyleData:
ddtheme.dropdownStyleData,
),
),
],
),
),
SizedBox(height: 10),
Row(
children: [
Text(
"Radius",
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w500,
),
),
Spacer(),
Text(
'${provider.currentValue.toStringAsFixed(2)} KM',
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w500,
),
),
],
),
InteractiveSlider(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 0),
min: 1.0,
max: 5.0, // MAX 5 KM
enabled: true,
foregroundColor: AppColors.app_blue,
segmentDividerColor: Color(0xFFF6F6F8),
onChanged: (value) {
// just to be extra safe, clamp in provider too
provider.currentValue = value;
},
),
// Slider(
// value: provider.currentValue,
// max: 100,
// divisions: 100,
//
// label: provider.currentValue.toStringAsFixed(2),
// inactiveColor: Color(0xFFD7D7D7),
// activeColor: AppColors.cyan_blue,
// onChanged: (value) {
// provider.currentValue = value;
// provider.debounce(() {
// provider.LoadNearbyGeneratorsAPI(context);
// }, Duration(milliseconds: 200));
// },
// ),
SizedBox(height: 30.0),
Container(
child: InkWell(
onTap: () {
provider.debounce(() {
provider.LoadNearbyGeneratorsAPI(
context,
provider.currentValue,
);
Navigator.pop(context);
}, Duration(milliseconds: 500));
},
child: Container(
alignment: Alignment.center,
height: 45,
margin: EdgeInsets.only(
left: 15.0,
right: 15.0,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14.0),
color: AppColors.app_blue,
),
child: Text(
"Search",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: 'JakartaMedium',
color: Colors.white,
),
),
),
),
),
],
),
],
),
),
);
},
),
);
},
);
},
);
}
}
......@@ -357,8 +357,21 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
),
child: Consumer<Leadlistprovider>(
builder: (context, provider, child) {
// 🔹 Initialize filter selections from widget.filter only once
if (provider.selectedLeadStatus == null &&
widget.filter?.status != null &&
widget.filter!.status!.toString().isNotEmpty) {
provider.selectedLeadStatus = widget.filter!.status;
}
if (provider.selectedOpenStatus == null &&
widget.filter?.openStatus != null &&
widget.filter!.openStatus!.toString().isNotEmpty) {
provider.selectedOpenStatus = widget.filter!.openStatus;
}
int selectedIndex = isSelected.indexWhere(
(element) => element == true,
(element) => element == true,
);
final headings = [
"Lead Status",
......@@ -373,6 +386,22 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
if (widget.mode != "executive") {
headings.add("Employee");
}
// int selectedIndex = isSelected.indexWhere(
// (element) => element == true,
// );
// final headings = [
// "Lead Status",
// "Open/Close Status",
// "Mobile Number",
// "Company Name",
// "Source",
// "Reference",
// "Team",
// "Segment",
// ];
if (widget.mode != "executive") {
headings.add("Employee");
}
return SizedBox(
height: MediaQuery.of(context).size.height * 0.7,
child: Column(
......@@ -512,17 +541,12 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
mainAxisSize: MainAxisSize.min,
children: [
if (selectedIndex == 0) ...[
...provider.leadStatusList.map((
status,
) {
...provider.leadStatusList.map((status,) {// here i want to do that i want to show selected from widget.filter!.status,
return SizedBox(
height: 35,
child: CheckboxListTile(
activeColor:
AppColors.app_blue,
controlAffinity:
ListTileControlAffinity
.leading,
activeColor: AppColors.app_blue,
controlAffinity: ListTileControlAffinity.leading,
checkboxShape: CircleBorder(
side: BorderSide(
width: 0.5,
......@@ -568,7 +592,7 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
width: 0.5,
),
),
title: Text(
title: Text(// same here widget.filter!.openStatus,
status ?? 'Unknown Status',
style: const TextStyle(
fontSize: 14,
......@@ -963,6 +987,7 @@ class _LeadlistbymodeState extends State<Leadlistbymode> {
},
);
}
void _showContactOptions(BuildContext context, String? phoneNumber) {
if (phoneNumber == null || phoneNumber.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
......
......@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:generp/Notifiers/crmProvider/addNewLeadsandProspectsProvider.dart';
import 'package:generp/screens/crm/CrmNearbyGenerators.dart';
import 'package:generp/screens/crm/addLeadsProspectsScreen.dart';
import 'package:generp/screens/crm/appointmentCalendar.dart';
import 'package:generp/screens/crm/followUpListonType.dart';
......@@ -741,6 +742,69 @@ class _CrmdashboardScreenState extends State<CrmdashboardScreen> {
],
),
),
SizedBox(height: 10,),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.only(left: 15, top: 10, right: 15),
child: Text(
"Nearby Generators",
style: TextStyle(
fontSize: 16,
color: AppColors.grey_semi,
),
),
),
),
SizedBox(height: 12,),
InkResponse(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CrmNearbyGenerators(),
),
);
},
child: Container(
height: 60,
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
height: 35,
width: 35,
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Color(0xFFEDF8FF),
shape: BoxShape.circle,
),
child: SvgPicture.asset(
"assets/svg/find_generator.svg",
),
),
),
Expanded(
flex: 4,
child: Text(
"Find Nearby Generators",
style: TextStyle(
fontSize: 14,
color: AppColors.app_blue,
),
),
),
],
),
),
),
if (provider.nearByLeads.isNotEmpty) ...[
Align(
......
......@@ -89,6 +89,12 @@ class _AllpaymentrequesitionlistsbymodesState
);
}
});
final providerclear = Provider.of<Requesitionlidtdetailsprovider>(
context,
listen: false,
);
providerclear.resetAll();
});
}
......@@ -859,6 +865,10 @@ class _AllpaymentrequesitionlistsbymodesState
],
),
),
errorWidget(
context,
provider.selectpaymentAccountError,
),
InkWell(
onTap: provider.isLoading
? null // Disable tap while loading
......@@ -871,23 +881,30 @@ class _AllpaymentrequesitionlistsbymodesState
// Small delay to ensure rebuild and show spinner before API starts
await Future.delayed(const Duration(milliseconds: 300));
// 🔄 Call API
await provider.paymentrequisitionApproveSubmitAPIFunction(
// Call API
if (remarks.text.isNotEmpty) {
await provider.paymentrequisitionApproveSubmitAPIFunction(
context,
widget.mode,
paymentID,
provider.approvedAmount.text,
safeNormalizeAmount(provider.approvedAmount.text),
remarks.text,
provider.selectedID,
);
await Provider.of<Requesitionlidtdetailsprovider>(
context,
listen: false,
);
} else {
provider.remarksError = "Please Enter Remarks.";
provider.selectpaymentAccountError = "Please Select Payment Account.";
}
// 🔓 Stop loading only if context is still active
// Stop loading only if context is still active
if (context.mounted) {
provider.isLoading = false;
}
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
......@@ -926,9 +943,19 @@ class _AllpaymentrequesitionlistsbymodesState
},
);
},
).then((value) {
).then((value) async {
final provider = Provider.of<Requesitionlidtdetailsprovider>(
context,
listen: false,
);
await provider.resetRequired();
var providerpage = Provider.of<Requestionlistprovider>(
context,
listen: false,
);
providerpage.paymentRequestionListsAPIFunction(context, widget.mode, "", "");
// This runs only when the bottom sheet is explicitly popped
print("closing Sheet");
// Use the original context with mounted check
if (context.mounted) {
......@@ -962,6 +989,23 @@ class _AllpaymentrequesitionlistsbymodesState
}
});
}
String safeNormalizeAmount(String? input) {
if (input == null || input.trim().isEmpty) return "0";
// Remove currency symbols, commas and spaces - keep digits and dots
String cleaned = input.replaceAll(RegExp(r'[^0-9.]'), '');
// Handle multiple decimal points: keep only first dot
int firstDot = cleaned.indexOf('.');
if (firstDot != -1) {
String before = cleaned.substring(0, firstDot + 1);
String after = cleaned.substring(firstDot + 1).replaceAll('.', '');
cleaned = before + after;
}
return cleaned.isEmpty ? "0" : cleaned;
}
Future<void> _showLevelRejectionSheet(BuildContext context, paymentID) {
return showModalBottomSheet(
......@@ -1130,7 +1174,7 @@ class _AllpaymentrequesitionlistsbymodesState
context,
listen: false,
);
detailsprov.resetAll();
detailsprov.resetRequired();
provider.paymentRequestionListsAPIFunction(
context,
......
......@@ -82,6 +82,7 @@ class _PaymentrequestionlistdetailsState
provider.showMoreDetails = false;
provider.checkDropDownReset();
print(widget.paymentRequestId);
provider.resetRequired();
provider.paymentRequesitionDetails(context, widget.paymentRequestId);
});
}
......@@ -891,9 +892,9 @@ class _PaymentrequestionlistdetailsState
),
),
Text(
reqDet.amount == ""
reqDet.amount == ""
? "-"
: "${reqDet.amount}",
: "${reqDet.amount }",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 14,
......@@ -936,8 +937,7 @@ class _PaymentrequestionlistdetailsState
final section = sections[sectionIndex];
final title = section["title"] as String;
final headings = section["headings"] as List<String>;
final subHeadings =
section["subHeadings"] as List<String>;
final subHeadings = section["subHeadings"] as List<String>;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
......@@ -1304,6 +1304,10 @@ class _PaymentrequestionlistdetailsState
}
Future<void> _showLevelApprovalSheet(BuildContext context, paymentID) {
final provider = Provider.of<Requesitionlidtdetailsprovider>(
context,
listen: false,
);
return showModalBottomSheet(
useSafeArea: true,
isDismissible: true,
......@@ -1516,9 +1520,9 @@ class _PaymentrequestionlistdetailsState
context,
widget.mode,
paymentID,
provider.approvedAmount.text,
safeNormalizeAmount(provider.approvedAmount.text),
remarks.text,
provider.selectedID,
provider.selectedID ?? "",
);
},
child: Container(
......@@ -1580,9 +1584,30 @@ class _PaymentrequestionlistdetailsState
},
);
},
);
).then((_) async {
await provider.resetRequired();
});
}
String safeNormalizeAmount(String? input) {
if (input == null || input.trim().isEmpty) return "0";
// Remove currency symbols, commas and spaces - keep digits and dots
String cleaned = input.replaceAll(RegExp(r'[^0-9.]'), '');
// Handle multiple decimal points: keep only first dot
int firstDot = cleaned.indexOf('.');
if (firstDot != -1) {
String before = cleaned.substring(0, firstDot + 1);
String after = cleaned.substring(firstDot + 1).replaceAll('.', '');
cleaned = before + after;
}
return cleaned.isEmpty ? "0" : cleaned;
}
Future<void> _showLevelRejectionSheet(BuildContext context, paymentID) {
return showModalBottomSheet(
useSafeArea: true,
......@@ -2268,6 +2293,7 @@ class _PaymentrequestionlistdetailsState
"Enter Proposed Payment Account",
(p0) {},
),
TextWidget(
context,
"Payment Account",
......@@ -2323,9 +2349,7 @@ class _PaymentrequestionlistdetailsState
value,
) {
if (value != null &&
provider
.paymentsAccounts
.isNotEmpty) {
provider.paymentsAccounts.isNotEmpty) {
setState(() {
provider.selectedPaymentAccounts =
value;
......@@ -2415,6 +2439,8 @@ class _PaymentrequestionlistdetailsState
],
),
),
errorWidget(context, provider.selectpaymentAccountError),
textControllerReadonlyWidget(
context,
provider.approvedAmountReadonly,
......@@ -2652,8 +2678,9 @@ class _PaymentrequestionlistdetailsState
),
child: InkWell(
onTap: () {
provider
.paymentrequisitionProcessSubmitAPIFunction(
HapticFeedback.mediumImpact();
if (provider.selectedPaymentAccounts != null) {
provider.paymentrequisitionProcessSubmitAPIFunction(
context,
widget.mode,
paymentReferenceNumber.text,
......@@ -2663,6 +2690,9 @@ class _PaymentrequestionlistdetailsState
remarks.text,
provider.imagePath,
);
} else{
provider.selectpaymentAccountError = "Please Select account";
}
},
child: Container(
height: 45,
......
......@@ -359,16 +359,19 @@ class _AttendanceRequestDetailScreenState
"Other Details",
scaleFactor,
),
if (details.type == "Check In" || details.type == "Check In/Out")
_buildDetailTile(
"Check In Type",
details.checkInType,
scaleFactor,
),
if (details.type == "Check Out" || details.type == "Check In/Out")
_buildDetailTile(
"Check Out Type",
details.chechOutType,
scaleFactor,
),
if (details.type == "Check Out" || details.type == "Check In/Out")
_buildDetailTile(
"Check Out Time",
details.checkOutTime,
......
......@@ -72,12 +72,12 @@ class _ContactListScreenState extends State<ContactListScreen> {
// Clean phone number
final cleanPhoneNumber = _cleanPhoneNumber(phoneNumber);
// Instead of inserting contact, open system form
// Instead of inserting contact, open system form
final newContact = Contact()
..name = Name(first: name)
..phones = [Phone(cleanPhoneNumber)];
// 🟢 This opens the phone’s native “Add contact” screen (prefilled)
// This opens the phone’s native “Add contact” screen (prefilled)
await FlutterContacts.openExternalInsert(newContact);
// Hide loading
......
......@@ -25,6 +25,7 @@ export 'package:generp/Notifiers/commonProvider/accountDetailsProvider.dart';
export 'package:generp/Notifiers/commonProvider/accountsListProvider.dart';
export 'package:generp/Notifiers/commonProvider/commonPagesProvider.dart';
export 'package:generp/Notifiers/commonProvider/accountLedgerProvider.dart';
export 'package:generp/Notifiers/commonProvider/editCommonAccountProvider.dart';
export 'package:generp/Notifiers/financeProvider/DashboardProvider.dart';
export 'package:generp/Notifiers/financeProvider/RequestionListProvider.dart';
......@@ -56,6 +57,7 @@ export 'package:generp/Notifiers/crmProvider/addProspectLeadsProvider.dart';
export 'package:generp/Notifiers/crmProvider/followUpUpdateProvider.dart';
export 'package:generp/Notifiers/crmProvider/appointmentCalendarProvider.dart';
export 'package:generp/Notifiers/crmProvider/addNewLeadsandProspectsProvider.dart';
export 'package:generp/Notifiers/crmProvider/CrmNearByGeneratorsProvider.dart';
export 'package:generp/Notifiers/hrmProvider/hrmAccessiblePagesProvider.dart';
export 'package:generp/Notifiers/hrmProvider/attendanceListProvider.dart';
......
......@@ -362,7 +362,7 @@ class _PaymentdetailsState extends State<Paymentdetails> {
),
errorWidget(context, provider.selectAmountError),
SizedBox(height: 10),
if (provider.selectPaymentMode?.name?.toLowerCase() != 'upi') ...[
if (provider.selectPaymentMode?.name?.toLowerCase() != 'qr') ...[
Padding(
padding: const EdgeInsets.only(bottom: 5.0),
child: Text("Reference Number"),
......@@ -481,7 +481,7 @@ class _PaymentdetailsState extends State<Paymentdetails> {
: () {
HapticFeedback.selectionClick();
// 🧩 Common validation
// Common validation
if (provider.selectContact == null) {
provider.selectContactError = "Please select phone number";
provider.notifyListeners();
......@@ -494,15 +494,15 @@ class _PaymentdetailsState extends State<Paymentdetails> {
return;
}
// 🧩 For UPI payment
if (provider.selectPaymentMode?.name?.toLowerCase() == 'upi') {
// For QR payment
if (provider.selectPaymentMode?.name?.toLowerCase() == 'qr') {
if (provider.imagePicked == 0 || provider.imagePath == null) {
provider.imageError = "Please upload reference document";
provider.notifyListeners();
return;
}
// All validations passed
// All validations passed
var homeProvider = Provider.of<HomescreenNotifier>(
context,
listen: false,
......@@ -521,7 +521,7 @@ class _PaymentdetailsState extends State<Paymentdetails> {
),
);
} else {
// 🧩 For non-UPI payments
// For non-QR payments
if (provider.Referencecontroller.text.isEmpty) {
provider.ReferenceError = "Please enter reference number";
provider.notifyListeners();
......@@ -561,7 +561,7 @@ class _PaymentdetailsState extends State<Paymentdetails> {
),
)
: Text(
provider.selectPaymentMode?.name?.toLowerCase() == 'upi'
provider.selectPaymentMode?.name?.toLowerCase() == 'qr'
? "Show QR"
: "Send OTP",
textAlign: TextAlign.center,
......
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
......@@ -31,6 +32,8 @@ class RazorpayQrScreen extends StatefulWidget {
class _RazorpayQrScreenState extends State<RazorpayQrScreen> {
Timer? _statusTimer;
bool _isCancelling = false;
@override
void initState() {
super.initState();
......@@ -54,7 +57,6 @@ class _RazorpayQrScreenState extends State<RazorpayQrScreen> {
}
});
}
void _startStatusPolling(QrProvider provider) {
_statusTimer = Timer.periodic(const Duration(seconds: 5), (timer) async {
if (!mounted) return;
......@@ -69,21 +71,15 @@ class _RazorpayQrScreenState extends State<RazorpayQrScreen> {
razorpayOrderId: razorpayOrderId,
);
if (response != null && response.error == "0") {//
if (response != null && response.error == "0") {
// success -> stop timer, notify user and pop
CustomSnackBar.showSuccess(
context: context,
message:"Payment received successfully ",
context: context,
message: "Payment received successfully",
);
// Fluttertoast.showToast(
// msg:
// backgroundColor: Colors.green,
// textColor: Colors.white,
// );
// Stop timer
timer.cancel();
// Go back two screens
if (mounted) {
Navigator.pop(context); // close QR screen
Navigator.pop(context); // close previous screen
......@@ -92,6 +88,67 @@ class _RazorpayQrScreenState extends State<RazorpayQrScreen> {
});
}
/// Called when user explicitly cancels / presses close.
/// This stops polling and checks the final status once — shows failure snack only if payment not completed.
Future<void> _handleCancel() async {
// Prevent re-entry
if (_isCancelling) return;
_isCancelling = true;
try {
// Stop polling immediately
_statusTimer?.cancel();
_statusTimer = null;
final provider = context.read<QrProvider>();
final razorpayOrderId = provider.qrResponse?.qrId;
if (razorpayOrderId != null) {
// Check status once more before showing failure
final response = await provider.fetchRazorpayUpiQrStatus(
context: context,
sessionId: widget.sessionId,
empId: widget.empId,
razorpayOrderId: razorpayOrderId,
);
// If response == null or not successful -> show warning now (single time)
if (response == null || response.error != "0") {
if (mounted) {
CustomSnackBar.showWarning(
context: context,
message: "⚠️ Payment not yet completed or failed",
);
}
} else {
if (mounted) {
CustomSnackBar.showSuccess(
context: context,
message: "Payment received successfully",
);
}
}
} else {
// No order id — just show failure once
if (mounted) {
CustomSnackBar.showWarning(
context: context,
message: "⚠️ Payment not yet completed or failed",
);
}
}
} catch (e) {
// ignore or log
debugPrint('_handleCancel error: $e');
} finally {
// Finally pop the screen (single pop)
if (mounted) Navigator.pop(context);
// small delay to reset guard if this screen somehow remains mounted longer
// but normally screen is popped so _isCancelling reset isn't necessary
}
}
@override
void dispose() {
_statusTimer?.cancel();
......@@ -100,33 +157,75 @@ class _RazorpayQrScreenState extends State<RazorpayQrScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: appbarNew(context, "Scan QR to Pay", 0xFFFFFFFF),
backgroundColor: Colors.black,
body: SafeArea(
child: Consumer<QrProvider>(
builder: (context, provider, _) {
if (provider.isLoading) {
return _buildLoadingState();
}
if (provider.errorMessage != null) {
return _buildErrorState(provider.errorMessage!);
}
if (provider.qrResponse == null || provider.qrResponse?.qrCode == null) {
return _buildNoQrState();
}
if (provider.secondsLeft == 0) {
Future.delayed(Duration.zero, () {
if (mounted) Navigator.pop(context);
});
return _buildExpiredState();
}
return _buildQrScreen(provider);
},
return WillPopScope(
onWillPop: () async {
await _handleCancel();
return false;
},
child: Scaffold(
appBar: AppBar(
backgroundColor: Color(0xFFFFFFFF),
automaticallyImplyLeading: false,
title: SizedBox(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
InkResponse(
onTap: () => _handleCancel(),
child: SvgPicture.asset(
"assets/svg/appbar_back_button.svg",
height: 25,
),
),
SizedBox(width: 10),
InkResponse(
onTap: () => _handleCancel(),
child: Text(
"Scan QR to Pay",
style: TextStyle(
fontSize: 16,
height: 1.1,
fontFamily: "JakartaSemiBold",
color: AppColors.semi_black,
),
),
),
],
),
),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.vertical(
// bottom: Radius.circular(30), // Adjust the radius as needed
// ),
// ),
),
backgroundColor: Colors.black,
body: SafeArea(
child: Consumer<QrProvider>(
builder: (context, provider, _) {
if (provider.isLoading) {
return _buildLoadingState();
}
if (provider.errorMessage != null) {
return _buildErrorState(provider.errorMessage!);
}
if (provider.qrResponse == null || provider.qrResponse?.qrCode == null) {
return _buildNoQrState();
}
if (provider.secondsLeft == 0) {
Future.delayed(Duration.zero, () {
if (mounted) Navigator.pop(context);
});
return _buildExpiredState();
}
return _buildQrScreen(provider);
},
),
),
),
);
......@@ -356,7 +455,7 @@ class _RazorpayQrScreenState extends State<RazorpayQrScreen> {
child: IconButton(
icon: const Icon(Icons.close, size: 18),
color: Colors.transparent,
onPressed: () => Navigator.pop(context),
onPressed: () => _handleCancel(),
),
),
),
......@@ -432,7 +531,7 @@ class _RazorpayQrScreenState extends State<RazorpayQrScreen> {
// Cancel Button
Expanded(
child: ElevatedButton.icon(
onPressed: () => Navigator.pop(context),
onPressed: () => _handleCancel(),
icon: const Icon(Icons.close, size: 18),
label: const Text("Cancel"),
style: ElevatedButton.styleFrom(
......
......@@ -7,9 +7,11 @@ import 'package:flutter_svg/svg.dart';
import 'package:generp/Notifiers/VisitDetailsProvider.dart';
import 'package:generp/Utils/app_colors.dart';
import 'package:generp/Utils/commonWidgets.dart';
import 'package:generp/screens/serviceEngineer/RazorpayQrScreen.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../Notifiers/HomeScreenNotifier.dart';
import '../../Utils/commonServices.dart';
import '../finance/FileViewer.dart';
import 'Followupdetails.dart';
......@@ -46,6 +48,7 @@ class _VisitdetailsState extends State<Visitdetails> {
visitdetails.showMoreDetails = false;
visitdetails.LoadVisitDetailsAPI(context, widget.complaintID);
visitdetails.LoadFollowupListAPI(context, widget.complaintID);
visitdetails.serviceComplaintBillList(context, widget.complaintID);
});
}
......@@ -90,6 +93,7 @@ class _VisitdetailsState extends State<Visitdetails> {
var generatorDetails = provider.generatorDetails;
var complaintDetails = provider.complaintDetailsNew;
var followups = provider.followUpList;
var complaintBillList = provider.complaintList;
return WillPopScope(
onWillPop: () => onBackPressed(context),
child: SafeArea(
......@@ -994,6 +998,129 @@ class _VisitdetailsState extends State<Visitdetails> {
),
),
],
if (complaintBillList.isNotEmpty) ...[
Container(
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
child: Text(
"Complaint Bill List",
style: TextStyle(
color: Color(0xFF818181),
fontFamily: "JakartaMedium",
),
),
),
SizedBox(
width: double.infinity,
height: 350,
child: ListView.builder(
padding: EdgeInsets.all(12),
itemCount: complaintBillList!.length,
itemBuilder: (context, index) {
final item = complaintBillList![index];
return Card(
margin: EdgeInsets.only(bottom: 12),
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// _detailRow("Bill ID", item.billId),
// _detailRow("Total Amount", item.totalAmount),
// _detailRow("Raw Amount", item.rawAmount),
// _detailRow("Narration", item.narration),
// _detailRow("Bill Date", item.billDate),
// _detailRow("Due Date", item.dueDate),
// _detailRow("Bill Paid", item.billPaid == "1" ? "Yes" : "No"),
Row(
children: [
// 👤 Avatar
Container(
height: 40,
width: 40,
decoration: const BoxDecoration(
color: Color(0xFFE6F6FF),
shape: BoxShape.circle,
),
clipBehavior: Clip.antiAlias,
child: SvgPicture.asset(
"assets/svg/compliant_list_ic.svg",
),
),
const SizedBox(width: 12),
// 📝 Info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.narration ?? "-",
style: const TextStyle(
fontFamily: "Plus Jakarta Sans",
fontWeight: FontWeight.w400,
fontSize: 14,
color: Colors.black87,
),
),
const SizedBox(height: 3),
Text(
item.dueDate ?? "-",
style: TextStyle(
fontFamily: "JakartaRegular",
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey.shade600,
height: 1.4,
),
),
],
),
),
// Call
if (item.billPaid == "0")
InkResponse(
onTap: (){
var homeProvider = Provider.of<HomescreenNotifier>(
context,
listen: false,
);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => RazorpayQrScreen(
sessionId: homeProvider.session,
empId: homeProvider.empId,
amount: item.rawAmount.toString(),
refType: "Bill",
refId: item.billId.toString()
))
);
},
child: Text("Pay Now", style: TextStyle(
fontSize: 14,
color: AppColors.app_blue,
),),
)
],
),
],
),
),
);
},
)
),
],
SizedBox(height: 75),
],
),
......@@ -1215,6 +1342,28 @@ class _VisitdetailsState extends State<Visitdetails> {
);
}
Widget _detailRow(String title, String? value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"$title: ",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
),
Expanded(
child: Text(
value ?? "--",
style: TextStyle(fontSize: 14),
),
),
],
),
);
}
Widget _scaffold(BuildContext context) {
return Consumer<Visitdetailsprovider>(
builder: (context, provider, child) {
......
......@@ -64,6 +64,7 @@ import '../Models/PaymentCollectionResponse.dart';
import '../Models/PaymentCollectionValidateOTPResponse.dart';
import '../Models/PaymentCollectionWalletResponse.dart';
import '../Models/ProfileResponse.dart';
import '../Models/ServiceComplaintBillListResponse.dart';
import '../Models/SessionResponse.dart';
import '../Models/StatusResponse.dart';
import '../Models/SubmitComplaintResponse.dart';
......@@ -78,6 +79,7 @@ import '../Models/UpdateComplaintResponse.dart';
import '../Models/UpdatePasswordResponse.dart';
import '../Models/VersionsResponse.dart';
import '../Models/ViewVisitDetailsResponse.dart';
import '../Models/commonModels/EditCommonAccFormDetailsResponse.dart';
import '../Models/commonModels/commonAddAccountsSubmitResponse.dart';
import '../Models/crmModels/appointmentCalendarResponse.dart';
import '../Models/crmModels/crmAddFollowUpResponse.dart';
......@@ -928,7 +930,7 @@ class ApiCalling {
};
final res = await post(data, technicianNearbyGeneratorsUrl, {});
if (res != null) {
// print(data);
print(" input data: $data");
// debugPrint(res.body);
return NearbyGeneratorsResponse.fromJson(jsonDecode(res.body));
} else {
......@@ -941,6 +943,36 @@ class ApiCalling {
}
}
static Future<NearbyGeneratorsResponse?> loadCrmNearbyGeneratorsAPI(
empId,
session,
techLoc,
radius,
status,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'emp_loc': (techLoc).toString(),
'radius': (radius).toString(),
};
final res = await post(data, crmNearbyGeneratorsUrl, {});
if (res != null) {
print(" input data: $data");
debugPrint("Map api response ${res.body}");
return NearbyGeneratorsResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<AccountSuggestionResonse?> AccountSuggestionAPI(
empId,
session,
......@@ -965,6 +997,34 @@ class ApiCalling {
return null;
}
}
/// service Complaint Bill List Api
static Future<ServiceComplaintBillListResponse?> serviceComplaintBillListAPI(
empId,
session,
complaintId,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'complaint_id': (complaintId).toString(),
};
debugPrint("@@@@@@@@@@@@@@@@@@@@@@ input data: $data");
final res = await post(data, serviceComplaintBillListUrl, {});
if (res != null) {
debugPrint(res.body);
return ServiceComplaintBillListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('###########Exception $e ');
return null;
}
}
static Future<TechnicianPendingComplaintsResponse?>
LoadTechnicianComplaintsAPI(empId, session) async {
......@@ -1218,15 +1278,17 @@ class ApiCalling {
session,
compId,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'comp_id': (compId).toString(),
};
debugPrint("&&&&&&&&&&&&&&&&&&&&&&& input: $data");
final res = await post(data, technicianComplaintFollowUpUrl, {});
if (res != null) {
debugPrint(res.body);
debugPrint("followup response ${res.body}");
return FollowupListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
......@@ -1750,6 +1812,68 @@ class ApiCalling {
}
}
static Future<CommonResponse?> commonUpdateAccountDetailsAPI(
empId,
sessionId,
accId,
name,
mob1,
mob2,
tel,
email,
designation,
address,
state,
district,
subLocality,
bankName,
branchName,
bankIfscCode,
accHolderName,
bankAccNumber,
bankUpiId,
) async {
try {
Map<String, String> data = {
'emp_id': empId.toString(),
'session_id': sessionId.toString(),
'acc_id': accId.toString(),
'name': name.toString(),
'mob1': mob1.toString(),
'mob2': mob2.toString(),
'tel': tel.toString(),
'email': email.toString(),
'designation': designation.toString(),
'address': address.toString(),
'state': state.toString(),
'district': district.toString(),
'sub_locality': subLocality.toString(),
'bank_name': bankName.toString(),
'bank_branch_name': branchName.toString(),
'bank_ifsc_code': bankIfscCode.toString(),
'bank_account_holder_name': accHolderName.toString(),
'bank_account_number': bankAccNumber.toString(),
'bank_upi_id': bankUpiId.toString(),
};
var res = await post(data, commonUpdateAccountDetailsUrl, {});
if (res != null) {
print("Input Date: $data");
debugPrint(res.body);
return CommonResponse.fromJson(jsonDecode(res.body),
);
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<paymentRequestionBankDetailsResponse?>
paymentRequestionBankDetailsAPI(empId, session, accountId) async {
try {
......@@ -2300,6 +2424,31 @@ class ApiCalling {
}
}
static Future<EditCommonAccFormDetailsResponse?> commonAccountDetailsListAPI(
empId,
session,
accountId,
) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'acc_id': accountId.toString(),
};
final res = await post(data, commonAccountDetailsUrl, {});
if (res != null) {
debugPrint(res.body);
return EditCommonAccFormDetailsResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
static Future<commonAccountLedgerFilterResponse?>
commonAccountLedgerDropDownAPI(empId, session) async {
try {
......@@ -5701,7 +5850,7 @@ class ApiCalling {
empId,
pageNumber
) async {
debugPrint("🔥🔥🔥🔥🔥🔥🔥Response");
debugPrint("Response");
try {
Map<String, String> data = {
'session_id': (session).toString(),
......@@ -5710,7 +5859,7 @@ class ApiCalling {
final res = await post(data, EmployeeContactListUrl, {});
if (res != null) {
print("Request Data: $data");
debugPrint("🔥🔥🔥🔥🔥🔥🔥 Response: ${res.body}");
debugPrint(" Response: ${res.body}");
return ContactListResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
......@@ -5739,6 +5888,7 @@ class ApiCalling {
'ref_id': (refId),
};
debugPrint("Input to QR : $data");
final res = await post(data, createRazorpayUpiQrUrl, {});
if (res != null) {
print(data);
......@@ -5821,4 +5971,29 @@ class ApiCalling {
// return null;
// }
// }
static Future<CommonResponse?> trackLiveLocationEmpolyeeAPI(empId, session,location) async {
try {
Map<String, String> data = {
'emp_id': (empId).toString(),
'session_id': (session).toString(),
'location': (location),
};
// print(data);
final res = await post(data, liveLocationStatusUrl, {});
if (res != null) {
print("BACKGROUND LOCATION DATA : ${data}");
debugPrint("BACKGROUND LOCATION: ${res.body}");
// print("check_session: ${res.body}");
return CommonResponse.fromJson(jsonDecode(res.body));
} else {
debugPrint("Null Response");
return null;
}
} catch (e) {
debugPrint('hello bev=bug $e ');
return null;
}
}
}
const baseUrl = "https://erp.gengroup.in/ci/app/";
const baseUrl_test = "https://erp.gengroup.in/ci/app/Api_home/";
const trackingUrl = "https://erp.gengroup.in/ci/app/Home/";
// var WEB_SOCKET_URL = "wss://ws.erp.gengroup.in/?type=user&route=employe_live_location_update&session_id=${Sessionid}";
const getAppVersionUrl = "https://erp.gengroup.in/ci/assets/appversion.json";
......@@ -31,6 +32,7 @@ const genTrackerRegisterComplaint = "${baseUrl}home/gen_tracker_register_complai
///complaint
const complaintsSelectionUrl = "${baseUrl}home/compliants_select_data";
const serviceComplaintBillListUrl = "${baseUrl_test}service_complaint_bill_list";
///inventory
const inventoryPartDetailsUrl = "${baseUrl}home/inventory_part_details";
......@@ -51,7 +53,7 @@ const technicianPaymentCollectionOtpUrl= "${baseUrl}home/technician_payment_coll
const technicianPaymentCollectionUrl= "${baseUrl}home/technician_payment_collection_list";
const technicianWalletCollectionUrl= "${baseUrl}home/technician_payment_collection_wallet";
const technicianComplaintDetailsUrl= "${baseUrl}home/technician_complaint_details";
const technicianComplaintFollowUpUrl= "${baseUrl}home/technician_complaint_followup_list";
const technicianComplaintFollowUpUrl= "${baseUrl}Home/technician_complaint_followup_list";
const technicianAddContactUrl= "${baseUrl}home/technician_add_contact";
const technicianUpdateVisitUrl= "${baseUrl}home/technician_update_visit";
......@@ -76,6 +78,8 @@ const paymentRequesitionPaymentsReceiptsDetailsUrl = "${baseUrl_test}payment_rec
const paymentRequesitionEditProcessedPaymentUrl = "${baseUrl_test}edit_processes_payment";
const validateGstNumberUrl = "${baseUrl_test}validate_gst_number";
const validateBankAccountDetailsUrl = "${baseUrl_test}validate_bank_account_details";
const commonUpdateAccountDetailsUrl = "${baseUrl_test}common_update_account_details";
///common Module
const commonAccessiblePagesUrl = "${baseUrl_test}common_accessible_pages";
......@@ -88,6 +92,7 @@ const commonAccountListUrl = "${baseUrl_test}common_account_list_v2";
const commonAccountLedgerDropDownUrl = "${baseUrl_test}common_account_ledger_list_view";
const commonAccountLedgerListWithFilterUrl = "${baseUrl_test}common_account_ledger_list_submit_filter_v2";
const commonAccountLedgerAccountDetails = "${baseUrl_test}common_account_details";
const commonAccountDetailsUrl = "${baseUrl_test}common_account_list_v2";
///order Module
const ordersAccessiblePagesUrl = "${baseUrl_test}crm_order_accessible_pages";
......@@ -183,6 +188,7 @@ const crmDashboardQuotationsUrl = "${baseUrl_test}crm_dashboard_quotations_list"
const ogcharturl = "${baseUrl_test}organisation_structures";
const JobDesciptionUrl ="${baseUrl_test}job_description";
const crmNearbyGeneratorsUrl= "${baseUrl_test}crm_nearby_generators";
///HRM
//Attendance
......@@ -211,4 +217,5 @@ const AdvanceListUrl ="${baseUrl_test}advance_list";
const createRazorpayUpiQrUrl ="${baseUrl_test}create_razorpay_upi_qr";
const fetchRazorpayUpiQrStatusUrl ="${baseUrl_test}fetch_razorpay_upi_qr_status";
\ No newline at end of file
const fetchRazorpayUpiQrStatusUrl ="${baseUrl_test}fetch_razorpay_upi_qr_status";
const liveLocationStatusUrl ="${trackingUrl}sattendance_live_location_update";
\ No newline at end of file
......@@ -445,10 +445,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.3.3"
ffi:
dependency: transitive
description:
......@@ -598,6 +598,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.12.0"
flutter_foreground_task:
dependency: "direct main"
description:
name: flutter_foreground_task
sha256: "9f1b25a81db95d7119d2c5cffc654048cbdd49d4056183e1beadc1a6a38f3e29"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
flutter_html:
dependency: "direct main"
description:
......@@ -1188,26 +1196,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "10.0.8"
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.9"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
......@@ -1889,10 +1897,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
version: "0.7.4"
version: "0.7.6"
timezone:
dependency: transitive
description:
......@@ -2033,10 +2041,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.2.0"
vm_service:
dependency: transitive
description:
......@@ -2134,5 +2142,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.7.2 <3.10.0-z"
dart: ">=3.8.0-0 <3.10.0-z"
flutter: ">=3.27.0"
......@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.107+115
version: 1.0.108+116
environment:
sdk: ^3.7.2
......@@ -95,6 +95,7 @@ dependencies:
photo_view: ^0.14.0
flutter_contacts: ^1.1.9+2
open_filex: ^4.7.0
flutter_foreground_task: ^9.1.0
dev_dependencies:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment