pos policy_validation invoice

This commit is contained in:
venbaittech 2025-12-19 18:42:42 +05:30
parent b9f2032d3b
commit bd4465caa5
36 changed files with 7612 additions and 2105 deletions

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,7 @@ import '../../presentation/screens/StaffAttendance/attendanceAllDetails.dart';
import '../../presentation/screens/StaffAttendance/individual_Attendance.dart';
import '../../presentation/screens/UserManagement/Agent/agent.dart';
import '../../presentation/screens/UserManagement/Agent/agentList.dart';
import '../../presentation/screens/UserManagement/POS/pos_list.dart';
import '../../presentation/screens/UserManagement/Profile/profile_mobile.dart';
import '../../presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart';
import '../../presentation/screens/UserManagement/SalesExecutive/salesExecutiveList.dart';
@ -85,57 +86,61 @@ final GoRouter appRouter = GoRouter(
builder: (context, state) => const AgentList(),
),
GoRoute(
path: '/agent/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
// if "create", open empty form
if (id == 'create') {
return const Agent(); // create mode
} else {
return Agent(id: id); // update mode
}
},
),
// GoRoute(
// path: '/agent/:id',
// builder: (context, state) {
// final id = state.pathParameters['id']!;
//
// // if "create", open empty form
// if (id == 'create') {
// return const Agent(); // create mode
// } else {
// return Agent(id: id); // update mode
// }
// },
// ),
GoRoute(
path: AppRoutes.staffLst,
// builder: (context, state) => const Agent(),
builder: (context, state) => const StaffList(),
),
// GoRoute(
// path: '/staff/:id',
// builder: (context, state) {
// final id = state.pathParameters['id']!;
// // if "create", open empty form
// if (id == 'create') {
// return const Staff(); // create mode
// } else {
// return Staff(id: id); // update mode
// }
// },
// ),
GoRoute(
path: '/staff/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
// if "create", open empty form
if (id == 'create') {
return const Staff(); // create mode
} else {
return Staff(id: id); // update mode
}
},
path: AppRoutes.posLst,
// builder: (context, state) => const Agent(),
builder: (context, state) => const PosList(),
),
GoRoute(
path: AppRoutes.salesExecutiveLst,
// builder: (context, state) => const Agent(),
builder: (context, state) => const SalesExecutiveList(),
),
GoRoute(
path: '/salesExecutive/:id',
builder: (context, state) {
final id = state.pathParameters['id']!;
// if "create", open empty form
if (id == 'create') {
return const SalesExecutive(); // create mode
} else {
return SalesExecutive(id: id); // update mode
}
},
),
// GoRoute(
// path: '/salesExecutive/:id',
// builder: (context, state) {
// final id = state.pathParameters['id']!;
// // if "create", open empty form
// if (id == 'create') {
// return const SalesExecutive(); // create mode
// } else {
// return SalesExecutive(id: id); // update mode
// }
// },
// ),
GoRoute(
path: AppRoutes.enquiryLst,
builder: (context, state) => const EnquiryList(),
@ -194,8 +199,10 @@ final GoRouter appRouter = GoRouter(
return CustomTransitionPage(
key: state.pageKey,
opaque: true, // required for open animation
barrierColor: Colors.black.withOpacity(0.3), // optional dim background
opaque: true, // required for open animation
barrierColor: Colors.black.withOpacity(
0.3,
), // optional dim background
fullscreenDialog: true,
transitionDuration: const Duration(milliseconds: 1000),
@ -210,7 +217,7 @@ final GoRouter appRouter = GoRouter(
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1), // slide from bottom
begin: const Offset(0, 1), // slide from bottom
end: Offset.zero,
).animate(curved),
child: child,

View File

@ -10,6 +10,7 @@ class AppRoutes {
static const staff = '/staff/:id';
static const String salesExecutiveLst = '/salesExecutiveLst';
static const salesExecutive = '/salesExecutive/:id';
static const String posLst = '/posLst';
static const String enquiryLst = '/enquiryLst';
static const String enquiryForStaff = '/enquiryForStaff';
static const String tabEnquiry = '/tabEnquiry';

View File

@ -307,6 +307,8 @@ class ApiService {
// return;
// }
final url = Uri.parse('https://venbait.in/nhance/partner/dev/$path');
// final url = Uri.parse('${Env.apiUrl}$path');
// final url = Uri.parse('${Env.apiUrl}$path');
print('getPdfDownload 2 - $url');
await _initializeToken();
@ -387,6 +389,183 @@ class ApiService {
}
}
Future<void> generateChartExcel(
// BuildContext context,
String path,
String id,
String month,
dynamic managerId,
) async {
print('getPdfDownload 1 - $path');
dynamic pathVal;
if (path == 'Insurer') {
pathVal =
'dashboard/downloadInsurerPoliciesExcel?manager_id=$managerId&month=$month&insurer_id=$id';
} else if (path == 'Broker') {
pathVal =
'dashboard/downloadBrokerPoliciesExcel?manager_id=$managerId&month=$month&broker_id=$id';
} else if (path == 'Product') {
pathVal =
'dashboard/downloadProductPoliciesExcel?manager_id=$managerId&month=$month&vehicle_type=$id';
} else if (path == 'PerformingTop50') {
pathVal =
'dashboard/downloadAgentMonthlyPoliciesExcel?manager_id=$managerId&agent_id=$id';
} else if (path == 'NonPerformingBelow50K') {
pathVal = 'dashboard/downloadLowPremiumAgentExcel?manager_id=$managerId';
} else {
//NoBusiness
pathVal =
'dashboard/downloadAgentsWithoutPoliciesExcel?manager_id=$managerId';
}
final url = Uri.parse('${Env.apiUrl}$pathVal');
// final url = Uri.parse('https://venbait.in/nhance/partner/dev/$path');
print('getPdfDownload 2 - $url');
await _initializeToken();
if (_token == null) throw Exception('Token not found. Please log in.');
final headers = {
'Authorization': 'Bearer $_token',
// 'App-Signature': Env.App_Signature,
'app-signature': Env.App_Signature,
};
final response = await _makeGethttpRequest(url, headers);
if (response.statusCode == 200) {
final contentType = response.headers['content-type'] ?? '';
if (contentType.contains('application/json')) {
final jsonResponse = jsonDecode(response.body);
if (jsonResponse['code'] == 404 || jsonResponse['code'] == 500) {
ToastHelper.show('File Not Found', Colors.red);
} else {
ToastHelper.show('No File Found', Colors.red);
}
return;
} else {
final blob = html.Blob([response.bodyBytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
print('getPdfDownload $blobUrl');
final contentDisp = response.headers['content-disposition'];
print('getPdfDownloadcontentDisp $contentDisp');
String fileName = extractFileName(contentDisp, path.split('/').last);
print('Resolved fileName: $fileName');
final anchor = html.AnchorElement(href: blobUrl)
..setAttribute('download', fileName)
..click();
html.Url.revokeObjectUrl(blobUrl);
}
} else if (response.statusCode == 404) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('File not found.'),
actions: [
TextButton(
child: const Text('OK'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
} else if (response.statusCode == 403) {
await clearLocalStorageAndRedirect();
} else if (response.statusCode == 500) {
ToastHelper.show('No File Found', Colors.red);
} else if (response.statusCode == 302) {
print('getPdfDownload 3-PP-302');
ToastHelper.show('No File Found', Colors.red);
} else {
ToastHelper.show('No File Found', Colors.red);
throw Exception('Failed to download file');
}
}
Future<void> generatePerformanceDashboardExcel(
// BuildContext context,
String path,
String id,
String salesId,
String month,
dynamic managerId,
) async {
print('getPdfDownload 1 - $path');
dynamic pathVal;
pathVal =
'dashboard/downloadStaffAndProductPoliciesExcel?manager_id=$managerId&month=$month&sales_executive_id=$salesId&vehicle_type=$id';
final url = Uri.parse('${Env.apiUrl}$pathVal');
// final url = Uri.parse('https://venbait.in/nhance/partner/dev/$path');
print('getPdfDownload 2 - $url');
await _initializeToken();
if (_token == null) throw Exception('Token not found. Please log in.');
final headers = {
'Authorization': 'Bearer $_token',
// 'App-Signature': Env.App_Signature,
'app-signature': Env.App_Signature,
};
final response = await _makeGethttpRequest(url, headers);
if (response.statusCode == 200) {
final contentType = response.headers['content-type'] ?? '';
if (contentType.contains('application/json')) {
final jsonResponse = jsonDecode(response.body);
if (jsonResponse['code'] == 404 || jsonResponse['code'] == 500) {
ToastHelper.show('File Not Found', Colors.red);
} else {
ToastHelper.show('No File Found', Colors.red);
}
return;
} else {
final blob = html.Blob([response.bodyBytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
print('getPdfDownload $blobUrl');
final contentDisp = response.headers['content-disposition'];
print('getPdfDownloadcontentDisp $contentDisp');
String fileName = extractFileName(contentDisp, path.split('/').last);
print('Resolved fileName: $fileName');
final anchor = html.AnchorElement(href: blobUrl)
..setAttribute('download', fileName)
..click();
html.Url.revokeObjectUrl(blobUrl);
}
} else if (response.statusCode == 404) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('File not found.'),
actions: [
TextButton(
child: const Text('OK'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
} else if (response.statusCode == 403) {
await clearLocalStorageAndRedirect();
} else if (response.statusCode == 500) {
ToastHelper.show('No File Found', Colors.red);
} else if (response.statusCode == 302) {
print('getPdfDownload 3-PP-302');
ToastHelper.show('No File Found', Colors.red);
} else {
ToastHelper.show('No File Found', Colors.red);
throw Exception('Failed to download file');
}
}
Future<void> downloadFile({
required String? apiUrl,
required PlatformFile? localFile,
@ -754,7 +933,9 @@ class ApiService {
} else if (role == 'Accounts') {
print('manager');
// url = Uri.parse('${Env.apiUrl}staff/staffList');
url = Uri.parse('${Env.apiUrl}staff/staffList?manager_id=${managerId}');
url = Uri.parse(
'${Env.apiUrl}salesExecutive/executiveList?manager_id=${managerId}',
);
} else {
print('handler');
url = Uri.parse(
@ -786,6 +967,36 @@ class ApiService {
return response;
}
// ----------------------------------- POS -------------------------------------------------
Future<Map<String, dynamic>> fetchPosList(int managerId, val) async {
// print(_token);
print('dropDown - $managerId');
if (_token == null) {
await _initializeToken();
}
dynamic url;
if (val == 'dropDown') {
print('dropDown');
url = Uri.parse(
'${Env.apiUrl}master/getAllPOS?is_active=1&manager_id=${managerId}',
);
} else {
print('List');
url = Uri.parse(
'${Env.apiUrl}master/getAllPOS?is_active=1&manager_id=${managerId}',
);
}
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'app-signature': Env.App_Signature,
};
final response = await _makeGetRequest(url, headers);
return response;
}
// ----------------------------------- ENQUIRY -------------------------------------------------
Future<Map<String, dynamic>> fetchEnquiryList(
@ -1504,14 +1715,19 @@ class ApiService {
// --------------------------------- PayOut Module----------------------------------------------
Future<Map<String, dynamic>> getCommissionRateList(data) async {
print("getCommissionRateList------- $data}");
final url = Uri.parse('${Env.apiUrl}/invoice/commission-rate-list');
print("getCommissionRateList 1");
// final token = await getToken(); // Fetch token
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
// if (_token == null) {
// throw Exception('Token not found. Please log in.');
// }
if (_token == null) {
await _initializeToken();
}
print("getCommissionRateList 2");
print("data------- $data}");
final headers = {
@ -1526,7 +1742,7 @@ class ApiService {
}
Future<Map<String, dynamic>> getCreateOrUpdate(data) async {
final url = Uri.parse('${Env.apiUrl}/invoice/create-or-update');
final url = Uri.parse('${Env.apiUrl}invoice/create-or-update');
// final token = await getToken(); // Fetch token
@ -1666,4 +1882,50 @@ class ApiService {
final response = await _makePostRequestJson(url, data, headers);
return response;
}
Future<Map<String, dynamic>> calculateCommissionRequest(data) async {
final url = Uri.parse('${Env.apiUrl}policy/calculateCommissionRequest');
// final token = await getToken(); // Fetch token
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
print("data------- $data}");
final headers = {
'Authorization': 'Bearer $_token',
'Content-Type': 'application/json',
'app-signature': Env.App_Signature,
// 'app-signature': 'nhance-partner-2025-signature-35468846JRhH551HK',
};
final response = await _makePostRequestJson(url, data, headers);
return response;
}
Future<Map<String, dynamic>> fetchPolicyMasterDropDown(String val) async {
// print(_token);
if (_token == null) {
await _initializeToken();
}
print('VAl - $val');
dynamic url;
if (val == 'fuelType') {
url = Uri.parse('${Env.apiUrl}policy/fuelTypeMaster');
} else if (val == 'vehicleType') {
url = Uri.parse('${Env.apiUrl}policy/vehicleTypeMaster');
}
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'app-signature': Env.App_Signature,
};
final response = await _makeGetRequest(url, headers);
return response;
}
}

View File

@ -67,8 +67,8 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
} else if (currentRoute.contains('broker') ||
currentRoute.contains('payment')) {
_activeMenu = 'Masters';
} else if (currentRoute.contains('invoice')) {
_activeMenu = 'Pay Out';
} else if (currentRoute.contains('payout')) {
_activeMenu = 'Invoice';
}
});
});
@ -155,6 +155,15 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
context.go(AppRoutes.salesExecutiveLst);
},
),
const SizedBox(height: 2),
_buildPopupItem(
label: "POS",
onTap: () {
_hidePopup();
setState(() => _activeMenu = 'User');
context.go(AppRoutes.posLst);
},
),
],
if (key == 'Reports') ...[
if (role == 'manager') ...[
@ -330,12 +339,12 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
// Pay Out button for Accounts
if (role == 'Accounts') ...[
_buildMenuItem(
isActive: _activeMenu == 'Pay Out',
isActive: _activeMenu == 'Invoice',
icon: Icons.checklist_outlined,
label: "Pay Out",
label: "Invoice",
onTap: () async {
_hidePopup();
setState(() => _activeMenu = 'Pay Out');
setState(() => _activeMenu = 'Invoice');
await _clearDashboardFilters();
context.go(AppRoutes.invoiceList);
},

View File

@ -12,6 +12,7 @@ import 'package:http/http.dart' as http;
import 'package:nhance_partner/core/routing/routes.dart';
import 'package:nhance_partner/data/utils/toastNotification.dart';
import 'package:nhance_partner/presentation/themes/indicators/text_field_theme.dart';
import 'package:nhance_partner/presentation/themes/indicators/text_field_theme_inline_editor.dart';
import '../../../../core/config/env.dart';
import '../../../../core/services/api_service.dart';
@ -69,7 +70,11 @@ class BrokerState extends ConsumerState<Broker> {
dynamic managerId;
Map<String, dynamic> dataDetails() {
final data = {"name": controllers["name"]?.text, "is_active": isActive};
final data = {
"name": controllers["name"]?.text,
"short_name": controllers["code"]?.text,
"is_active": isActive,
};
return data;
}
@ -108,12 +113,16 @@ class BrokerState extends ConsumerState<Broker> {
setState(() {
selectedId = id;
controllers['name']?.text = data['name'] ?? '';
controllers['code']?.text = data['short_name'] ?? '';
controllers["is_active"]?.text = data['is_active'].toString();
});
}
}
Future<void> handleSave() async {
// if (controllers['name'] != null && controllers['name'] != '') {
// return;
// }
if (!_formKey.currentState!.validate()) return;
setState(() {
if (_formKey.currentState!.validate()) {
@ -185,17 +194,12 @@ class BrokerState extends ConsumerState<Broker> {
print("Broker submitted successfully!");
print("Response: ${response.body}");
if(responseBody['status'] == 200){
if (responseBody['status'] == 200) {
refresh();
isUpdating
? ToastHelper.showSuccessToast(
context,message
)
: ToastHelper.showSuccessToast(
context,message,
);
? ToastHelper.showSuccessToast(context, message)
: ToastHelper.showSuccessToast(context, message);
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
@ -281,7 +285,9 @@ class BrokerState extends ConsumerState<Broker> {
Widget buildFormFields() {
return Form(
key: _formKey,
child: Column(children: [buildName()]),
child: Row(
children: [buildName(), SizedBox(width: 10), buildShortName()],
),
);
}
@ -291,13 +297,39 @@ class BrokerState extends ConsumerState<Broker> {
children: [
Text("Broker *", style: _textStyle),
SizedBox(width: 10),
ThemedFormField(
controller: controllers['name']!,
validator: (value) => Validators.requiredField(value, "name"),
txtwidth: MediaQuery.of(context).size.width * 0.15,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
txtheight: 30,
SizedBox(
child: ThemedFormInlineField(
controller: controllers['name']!,
validator: (value) => Validators.requiredField(value, "name"),
txtwidth: MediaQuery.of(context).size.width * 0.15,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
// txtheight: 30,
isdense: true,
errFieldHgt: 0,
),
),
],
);
}
Widget buildShortName() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Broker Code *", style: _textStyle),
SizedBox(width: 10),
SizedBox(
child: ThemedFormInlineField(
controller: controllers['code']!,
validator: (value) => Validators.requiredField(value, "code"),
txtwidth: MediaQuery.of(context).size.width * 0.15,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
// txtheight: 30,
isdense: true,
errFieldHgt: 0,
),
),
],
);

View File

@ -283,9 +283,13 @@ class BrokerListState extends ConsumerState<BrokerList> {
child: Text('S.No.', style: _headerStyle),
),
Expanded(
flex: 2,
flex: 1,
child: Text('Broker', style: _headerStyle),
),
Expanded(
flex: 1,
child: Text('Broker Code', style: _headerStyle),
),
Expanded(
flex: 1,
@ -383,7 +387,11 @@ class BrokerListState extends ConsumerState<BrokerList> {
child: Row(
children: [
Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)),
Expanded(flex: 2, child: Text(item['name'] ?? '-', style: _dataBold)),
Expanded(flex: 1, child: Text(item['name'] ?? '-', style: _dataBold)),
Expanded(
flex: 1,
child: Text(item['short_name'] ?? '-', style: _dataBold),
),
Expanded(
flex: 1,
child: Row(
@ -466,17 +474,16 @@ class BrokerListState extends ConsumerState<BrokerList> {
);
}
static final _dataBold = GoogleFonts.inter(
fontSize: 11.5,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _dataSub = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w300,
color: Color(0xFF585757),
);
static final _dataBold = GoogleFonts.inter(
fontSize: 11.5,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _headerStyle = GoogleFonts.poppins(
fontSize: 11.2,

View File

@ -114,6 +114,12 @@ class PaymentState extends ConsumerState<Payment> {
}
Future<void> handleSave() async {
if (controllers['value'] == null ||
controllers['value']!.text.trim().isEmpty) {
ToastHelper.showWarningToast(context, "Payment Mode is required");
return;
}
if (!_formKey.currentState!.validate()) return;
setState(() {
if (_formKey.currentState!.validate()) {
@ -194,38 +200,33 @@ class PaymentState extends ConsumerState<Payment> {
print("Response: ${response.body}");
print(message);
if(responseBody['status'] == 200){
refresh();
isUpdating
? ToastHelper.showSuccessToast(
context,message,
)
: ToastHelper.showSuccessToast(
context,message,
);
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Payment Creation Failed"),
content: Text(message),
actions: [
TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
if (responseBody['status'] == 200) {
refresh();
isUpdating
? ToastHelper.showSuccessToast(context, message)
: ToastHelper.showSuccessToast(context, message);
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Payment Creation Failed"),
content: Text(message),
actions: [
TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
}
@ -302,11 +303,13 @@ class PaymentState extends ConsumerState<Payment> {
SizedBox(width: 10),
ThemedFormField(
controller: controllers['value']!,
validator: (value) => Validators.requiredField(value, "value"),
// validator: (value) => Validators.requiredField(value, "value"),
txtwidth: MediaQuery.of(context).size.width * 0.15,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
txtheight: 30,
isdense: true,
errFieldFont: 0,
),
],
);

View File

@ -35,7 +35,9 @@ import 'package:universal_html/html.dart' as html;
class Agent extends ConsumerStatefulWidget {
final String? id;
const Agent({super.key, this.id});
final void Function(String value) onSubmit;
const Agent({super.key, this.id, required this.onSubmit});
@override
ConsumerState<Agent> createState() => AgentState();
}
@ -43,7 +45,14 @@ class Agent extends ConsumerStatefulWidget {
class AgentState extends ConsumerState<Agent> {
final _formKey = GlobalKey<FormState>();
late ApiService apiService;
List<String> tabHeader = ['name', 'email', 'mobile', 'code', 'address'];
List<String> tabHeader = [
'name',
'email',
'mobile',
'code',
'address',
'retenRate',
];
late String isActive = "1";
// html.File? docUploadedFile;
// PlatformFile? passportFile;
@ -73,6 +82,7 @@ class AgentState extends ConsumerState<Agent> {
"mobile": controllers["mobile"]?.text,
"address": controllers["address"]?.text,
"agent_code": controllers["code"]?.text,
"retention_rate": controllers["retenRate"]?.text,
'sales_executive_id': selectedSalesExectv,
"is_active": isActive,
"manager_id": managerId,
@ -85,6 +95,8 @@ class AgentState extends ConsumerState<Agent> {
super.initState();
apiService = ApiService();
print('AGID - ${widget.id}');
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
@ -156,8 +168,10 @@ class AgentState extends ConsumerState<Agent> {
controllers['mobile']?.text = data['mobile'] ?? '';
controllers['code']?.text = data['agent_code'] ?? '';
controllers['address']?.text = data['address'] ?? '';
controllers['retenRate']?.text = data['retention_rate'] ?? '';
isActive = data["is_active"];
selectedSalesExectv = data['sales_executive_id'] ?? '';
print('selectedSalesExectv - $selectedSalesExectv');
String? apiDocPath = data["certificate_file_name"];
if (apiDocPath != null && apiDocPath.isNotEmpty) {
@ -182,7 +196,7 @@ class AgentState extends ConsumerState<Agent> {
final email = controllers['email']!.text.trim();
final phone = controllers['mobile']!.text.trim();
// 🔴 Conditional validation: at least one required
// Conditional validation: at least one required
if (email.isEmpty && phone.isEmpty) {
ToastHelper.showSuccessToast(
context,
@ -317,7 +331,11 @@ class AgentState extends ConsumerState<Agent> {
context,
'Partner Created Successfully',
);
context.go(AppRoutes.agentLst);
Navigator.of(context).pop();
widget.onSubmit("success");
// context.go(AppRoutes.agentLst);
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
@ -395,11 +413,12 @@ class AgentState extends ConsumerState<Agent> {
Widget build(BuildContext context) {
// managerId = ref.watch(managerIdProvider);
// userId = ref.watch(userProvider);
return MainLayout(
title: "Partner",
body: Container(
return AlertDialog(
backgroundColor: Colors.white,
content: Container(
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width * 0.62,
height: MediaQuery.of(context).size.height * 0.7,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
@ -407,7 +426,7 @@ class AgentState extends ConsumerState<Agent> {
Container(
// height: 30,
// color: Colors.red.shade50,
width: MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width * 0.8,
child: GestureDetector(
onTap: () {
context.go(AppRoutes.agentLst);
@ -416,24 +435,24 @@ class AgentState extends ConsumerState<Agent> {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Tooltip(
message: 'Back',
child: IconButton(
icon: const Icon(
Icons.arrow_left_sharp,
size: 25,
color: Color(0xFF425B5B),
),
onPressed: () {
context.go(AppRoutes.agentLst);
},
splashRadius: 18,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
),
),
const SizedBox(width: 5), // spacing between icon and text
// Tooltip(
// message: 'Back',
// child: IconButton(
// icon: const Icon(
// Icons.arrow_left_sharp,
// size: 25,
// color: Color(0xFF425B5B),
// ),
// onPressed: () {
// context.go(AppRoutes.agentLst);
// },
// splashRadius: 18,
// hoverColor: Colors.black12,
// padding: const EdgeInsets.all(4),
// constraints: const BoxConstraints(),
// ),
// ),
const SizedBox(width: 15), // spacing between icon and text
Text(
"Partner",
style: GoogleFonts.poppins(
@ -441,16 +460,32 @@ class AgentState extends ConsumerState<Agent> {
fontWeight: FontWeight.w500,
),
),
Spacer(),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: const Color(0xFFF1F1F1),
borderRadius: BorderRadius.circular(5.0),
),
child: Tooltip(
message: 'Close',
child: const Icon(Icons.close, size: 18),
),
),
),
// const SizedBox(width: 15),
],
),
),
),
SizedBox(height: 10),
// SizedBox(height: 10),
Expanded(
child: Container(
// color: Colors.green,
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.all(8.0),
// width: MediaQuery.of(context).size.width,
// margin: EdgeInsets.all(8.0),
padding: EdgeInsets.symmetric(horizontal: 14.0, vertical: 20.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
@ -459,7 +494,7 @@ class AgentState extends ConsumerState<Agent> {
),
child: Column(
children: [
Row(children: [Expanded(child: buildFormFields())]),
Row(children: [buildFormFields()]),
Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
@ -507,39 +542,42 @@ class AgentState extends ConsumerState<Agent> {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: buildName()),
buildName(),
SizedBox(width: 25),
Expanded(child: buildEmail()),
buildEmail(),
SizedBox(width: 25),
buildPhNumber(),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(child: buildPhNumber()),
buildId(),
SizedBox(width: 25),
Expanded(child: buildId()),
buildSalesExecutive(context),
SizedBox(width: 25),
buildRetentionRate(context),
],
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: buildSalesExecutive(context)),
buildAddress(),
SizedBox(width: 25),
Expanded(child: buildAddress()),
buildUploadDocument(),
SizedBox(width: 25),
// Expanded(child: SizedBox.shrink()),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(child: buildUploadDocument()),
SizedBox(width: 25),
Expanded(child: SizedBox.shrink()),
],
),
// Row(
// Column(
// children: [
// Expanded(child: buildIncentiveFile()),
// SizedBox(width: 25),
@ -552,28 +590,29 @@ class AgentState extends ConsumerState<Agent> {
}
Widget buildName() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Full Name *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['name']!,
validator: (value) => Validators.requiredField(value, "name"),
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildEmail() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Email *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['email']!,
validator: (value) {
@ -591,18 +630,18 @@ class AgentState extends ConsumerState<Agent> {
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildPhNumber() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Phone Number *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['mobile']!,
validator: (value) {
@ -621,18 +660,18 @@ class AgentState extends ConsumerState<Agent> {
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildId() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Partner Id *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['code']!,
inputFormatters: [
@ -641,23 +680,55 @@ class AgentState extends ConsumerState<Agent> {
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
validator: (value) => Validators.requiredField(value, "id"),
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildRetentionRate(context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Retention Rate*", style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['retenRate']!,
inputFormatters: [
TextInputFormatter.withFunction((oldValue, newValue) {
if (newValue.text.isEmpty) return newValue;
final value = double.tryParse(newValue.text);
if (value == null) return oldValue;
// Allow only values <= 10
if (value <= 10) {
return newValue;
}
return oldValue;
}),
],
keyboardType: TextInputType.numberWithOptions(decimal: true),
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
validator: (value) => Validators.requiredField(value, "id"),
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildAddress() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Address", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['address']!,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[ a-zA-Z0-9_#/.]')),
],
@ -669,17 +740,20 @@ class AgentState extends ConsumerState<Agent> {
Widget buildSalesExecutive(BuildContext context) {
Map<String, dynamic>? selectedroleVal = filteredSalesExecutiveData
.firstWhere(
(item) => item['id'].toString() == selectedSalesExectv,
(item) => item['id'] == selectedSalesExectv,
orElse: () => {},
);
final isReadOnly = widget.id != null;
print('selectedroleVal - $selectedroleVal');
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
final isReadOnly = false;
// final isReadOnly = widget.id != null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Sales Executive *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
// color: Colors.white,
@ -687,7 +761,7 @@ class AgentState extends ConsumerState<Agent> {
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
: MediaQuery.of(context).size.width * 0.18,
// height: 40,
child: AbsorbPointer(
absorbing: isReadOnly,
@ -815,11 +889,11 @@ class AgentState extends ConsumerState<Agent> {
}
Widget buildUploadDocument() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Upload Certificate", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
Column(
mainAxisAlignment: MainAxisAlignment.end,
@ -827,7 +901,8 @@ class AgentState extends ConsumerState<Agent> {
children: [
ThemedUploadField(
hintText: selectedFileNames ?? "Upload Document",
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
txtheight: 45,
borderColor: Color(0xFFE2E8F0),
// highlightColor: Color(0xFF50A398),
onFileSelected: (fileName, file) {
@ -842,7 +917,7 @@ class AgentState extends ConsumerState<Agent> {
if (docUploadedFile != null || passportFileUrlFromApi != null)
Container(
// color: Colors.white,
child: Row(
child: Column(
children: [
// IconButton(
// onPressed: () {
@ -873,7 +948,7 @@ class AgentState extends ConsumerState<Agent> {
// color: Color(0xFF425B5B),
// color: Colors.green.shade300,
),
child: Row(
child: Column(
children: const [
Text(
"Download",
@ -901,7 +976,7 @@ class AgentState extends ConsumerState<Agent> {
// children: [
// ThemedUploadField(
// hintText: selectedFileNames ?? "Upload Document",
// txtwidth: MediaQuery.of(context).size.width * 0.26,
// txtwidth: MediaQuery.of(context).size.width * 0.18,
//
// onFileSelected: (fileName, file) {
// // print("File picked: ${fileName}");
@ -920,7 +995,7 @@ class AgentState extends ConsumerState<Agent> {
// // Centers the text
// Container(
// color: Colors.white,
// child: Row(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.end,
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
@ -1009,7 +1084,7 @@ class AgentState extends ConsumerState<Agent> {
// borderRadius: BorderRadius.circular(5),
// color: Colors.green.shade300,
// ),
// child: Row(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.end,
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [

View File

@ -15,6 +15,7 @@ import '../../../themes/indicators/filter_btn.dart';
import '../../../themes/indicators/search_field_theme.dart';
import '../../../themes/indicators/text_field_theme.dart';
import '../../../widgets/custom_action_popup.dart';
import 'agent.dart';
import 'agentIncentiveFile.dart';
class AgentList extends ConsumerStatefulWidget {
@ -28,12 +29,14 @@ class AgentListState extends ConsumerState<AgentList> {
int itemsPerPage = 10;
late ApiService apiService;
dynamic managerId;
dynamic selected_id;
// List<Map<String, dynamic>> dataVal = [];
List<Map<String, dynamic>> getAgentData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
dynamic sortedData;
dynamic managerID;
@override
void initState() {
super.initState();
@ -41,13 +44,19 @@ class AgentListState extends ConsumerState<AgentList> {
// getAgentList();
Future.microtask(() {
final id = ref.read(managerIdProvider);
if (id != null) {
getAgentList(id);
managerID = ref.read(managerIdProvider);
if (managerID != null) {
getAgentList(managerID);
}
});
}
void refresh() {
if (managerID != null) {
getAgentList(managerID);
}
}
// @override
// void didChangeDependencies() {
// super.didChangeDependencies();
@ -162,6 +171,20 @@ class AgentListState extends ConsumerState<AgentList> {
];
}
Future<void> showAgent({required String id}) {
print('showAgent : $id');
return showDialog(
context: context,
builder: (ctx) => Agent(
id: id,
onSubmit: (value) {
debugPrint("New Claims: $value");
refresh();
},
),
);
}
@override
Widget build(BuildContext context) {
managerId = ref.watch(managerIdProvider);
@ -301,6 +324,7 @@ class AgentListState extends ConsumerState<AgentList> {
'Email',
'Phone Number',
'Id',
'Retention Rate',
'Address',
'Status',
],
@ -310,6 +334,7 @@ class AgentListState extends ConsumerState<AgentList> {
"email",
"mobile",
"agent_code",
"retention_rate",
"address",
"is_active",
],
@ -318,7 +343,8 @@ class AgentListState extends ConsumerState<AgentList> {
SizedBox(width: 10),
InkWell(
onTap: () {
context.go('/agent/create');
// context.go('/agent/create');
showAgent(id: 'Create');
},
child: Container(
padding: EdgeInsets.all(5.0),
@ -391,6 +417,13 @@ class AgentListState extends ConsumerState<AgentList> {
flex: 1,
child: Text('Id', style: _headerStyle),
),
Expanded(
flex: 1,
child: Text(
'Retention Rate ',
style: _headerStyle,
),
),
Expanded(
flex: 2,
child: Text('Address', style: _headerStyle),
@ -511,6 +544,10 @@ class AgentListState extends ConsumerState<AgentList> {
flex: 1,
child: Text(item['agent_code'] ?? '-', style: _dataBold),
),
Expanded(
flex: 1,
child: Text(item['retention_rate'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(
@ -567,8 +604,10 @@ class AgentListState extends ConsumerState<AgentList> {
),
onPressed: () {
print('EDITStaff - ${item['id']}');
dynamic id = item['id'];
context.go('/agent/$id');
selected_id = item['id'];
showAgent(id: selected_id);
// context.go('/agent/$selected_id');
},
splashRadius: 28,
hoverColor: Colors.black12,

View File

@ -0,0 +1,569 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/utils/Pagination.dart';
import '../../../layouts/main_layout.dart';
import '../../../layouts/responsive_layout.dart';
import '../../../providers/manager_provider.dart';
import '../../../themes/indicators/export_btn.dart';
import '../../../themes/indicators/filter_btn.dart';
import '../../../themes/indicators/search_field_theme.dart';
import '../../../themes/indicators/text_field_theme.dart';
import '../../../widgets/custom_action_popup.dart';
class PosList extends ConsumerStatefulWidget {
const PosList({super.key});
@override
ConsumerState<PosList> createState() => PosListState();
}
class PosListState extends ConsumerState<PosList> {
int currentPage = 1;
int itemsPerPage = 10;
late ApiService apiService;
dynamic managerId;
dynamic role;
dynamic prefid;
// List<Map<String, dynamic>> dataVal = [];
List<Map<String, dynamic>> getStaffData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
@override
void initState() {
super.initState();
apiService = ApiService();
Future.microtask(() {
final data1 = ref.read(managerIdProvider);
prefid = data1;
// role = ref.read(userRoleProvider);
// print("E43 => mId: $prefid");
// print("roleSTAFFLIST: $role");
if (prefid != null) {
getPosList(prefid);
}
});
// getPosList();
}
//
// @override
// void didChangeDependencies() {
// super.didChangeDependencies();
// final id = ref.watch(managerIdProvider);
// if (id != null) {
// getPosList(id);
// }
// }
List<dynamic> get _paginatedData {
// Sort descending by id first
final sortedData = [...filteredData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
if (sortedData.isEmpty) return [];
// Ensure currentPage is valid
final maxPage = (sortedData.length / itemsPerPage).ceil();
final safePage = currentPage.clamp(1, maxPage);
final startIndex = (safePage - 1) * itemsPerPage;
final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length);
return sortedData.sublist(startIndex, endIndex);
}
// void filterData(String query) {
// print("FilterDAta - $query");
// setState(() {
// filteredData = getStaffData.where((item) {
// final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
//
// return (item['name'] ?? '-').toLowerCase().contains(
// query.toLowerCase(),
// ) ||
// (item['email'] ?? '-').toLowerCase().contains(
// query.toLowerCase(),
// ) ||
// (item['mobile'] ?? '-').toLowerCase().contains(
// query.toLowerCase(),
// ) ||
// (item['address'] ?? '-').toLowerCase().contains(
// query.toLowerCase(),
// ) ||
// (item['emp_id'] ?? item['agent_code'] ?? '-')
// .toLowerCase()
// .contains(query.toLowerCase()) ||
// isActiveStatus.contains(query.toLowerCase());
// }).toList();
// });
// }
void filterData(String query) {
setState(() {
final q = query.toLowerCase();
if (q.isEmpty) {
filteredData = getStaffData;
return;
}
filteredData = getStaffData.where((item) {
final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
return (item['name'] ?? '').toString().toLowerCase().contains(q) ||
(item['email'] ?? '').toString().toLowerCase().contains(q) ||
(item['mobile'] ?? '').toString().toLowerCase().contains(q) ||
(item['address'] ?? '').toString().toLowerCase().contains(q) ||
(item['emp_id'] ?? item['agent_code'] ?? '')
.toString()
.toLowerCase()
.contains(q) ||
isActiveStatus.contains(q);
}).toList();
});
}
final TextEditingController _searchStaffController = TextEditingController();
Future<void> getPosList(int id) async {
print('E104 => Fns called => $id');
final val = 'list';
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchPosList(id, val);
if (response['status'] == 200) {
print('E113 => getStaffListData => ${response['data']}');
setState(() {
getStaffData = List<Map<String, dynamic>>.from(response['data']);
originalData = getStaffData;
filteredData = List.from(originalData);
// print('originalData - $getClaimPolicies');
});
} else {
getStaffData = [];
originalData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
List<Widget> _buildPopupMenuActions(BuildContext context, dynamic data) {
return [
GestureDetector(
onTap: () {
Navigator.pop(context);
print('EDITStaff - ${data['id']}');
dynamic id = data['id'];
context.go('/pos/$id');
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.edit_sharp, color: Color(0xFF319718), size: 18),
SizedBox(width: 10),
Text('Edit'),
],
),
),
];
}
@override
Widget build(BuildContext context) {
managerId = ref.watch(managerIdProvider);
return MainLayout(
title: "Pos",
body: Container(
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
child: Container(
// color: Colors.green,
// color: Colors.green.shade50,
width: MediaQuery.of(context).size.width,
// margin: EdgeInsets.all(10.0),
decoration: BoxDecoration(
// color: Colors.white,
borderRadius: BorderRadius.circular(15.0),
),
padding: EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
// height: 40,
// color: Colors.pink,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'POS',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
Spacer(),
ThemedSearchField(
hintText: 'Search',
// backgroundColor: Color(0xFFF6F8F8),
backgroundColor: Color(0xFFFFFFFF),
txtHeight: 30,
onChanged: filterData,
controller: _searchStaffController,
txtwidth: MediaQuery.of(context).size.width * 0.15,
),
SizedBox(width: 10),
ExportBtn(
sheetName: "POS",
fileName: "pos_list",
txt: !ResponsiveLayout.isMobile(context)
? true
: false,
data: filteredData,
displayHeaders: [
'S.No.',
'Pos Name',
'Code',
'Email',
'Mobile',
'Bank',
'Aadhar',
'PAN',
],
keys: [
"sno", // handled internally as i + 1
"name",
"pos_code",
"email",
"mobile",
"bank_name",
"aadhar",
"pan",
// "is_active",
],
),
// SizedBox(width: 10),
// InkWell(
// onTap: () {
// context.go('/pos/create');
// },
// child: Container(
// padding: EdgeInsets.all(4.8),
// decoration: BoxDecoration(
// color: const Color(0xFF2E7D6E),
// borderRadius: BorderRadius.circular(8.0),
// ),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Tooltip(
// message: 'Create Pos',
// child: Icon(
// Icons.add,
// color: Colors.white,
// size: 18,
// ),
// ),
// ],
// ),
// ),
// ),
],
),
),
SizedBox(height: 5),
Container(
decoration: BoxDecoration(
color: Color(0xFFF1F5F9),
// color: Color(0xFFEDF6F5),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
child: Row(
children: [
Expanded(
flex: 1,
child: Text('S.No.', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Name', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Code', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Email', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Mobile', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Bank', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Aadhar', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('PAN', style: _headerStyle),
),
// Expanded(
// flex: 1,
// child: Text('Status', style: _headerStyle),
// ),
],
),
),
Expanded(child: _buildDataTable(context)),
],
),
),
),
Container(
// height: 20,
width: MediaQuery.of(context).size.width,
// color: Colors.green.shade50,
child: PaginationControls(
currentPage: currentPage,
itemsPerPage: itemsPerPage,
// totalItems: dataVal.length,
totalItems: filteredData.length,
// activeColor: layoutColor, // your theme color
onPageChanged: (page) {
setState(() {
currentPage = page;
});
},
onItemsPerPageChanged: (items) {
setState(() {
itemsPerPage = items;
currentPage = 1;
});
},
),
),
],
),
),
);
}
Widget _buildDataTable(BuildContext context) {
if (filteredData.isEmpty) {
return const SizedBox(
height: 50,
child: Center(child: Text('No available data')),
);
}
final sortedData = [..._paginatedData];
return ListView.builder(
// itemCount: filteredData.length + 1, // +1 for header, +1 for pagination
itemCount: sortedData.length + 1, // +1 for header, +1 for pagination
itemBuilder: (context, index) {
if (index == 0) return _buildHeader();
// if (index == dataVal.length + 1)
// return _buildPagination(context);
final startIndex = ((currentPage - 1) * itemsPerPage);
// final item = filteredData[index - 1];
final item = sortedData[index - 1];
final sno = startIndex + index;
return _buildDataRow(item, sno);
},
);
}
Widget _buildHeader() {
return SizedBox.shrink();
}
Widget _buildDataRow(Map<String, dynamic> item, sno) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
// margin: const EdgeInsets.only(top: 10),
decoration: BoxDecoration(
color: Colors.white,
// color: Color(0xFFE0F7F9),
border: const Border(
bottom: BorderSide(color: Colors.blueGrey, width: 0.15),
),
// borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(flex: 1, child: Text('$sno' ?? '-', style: _dataBold)),
Expanded(flex: 2, child: Text(item['name'] ?? '-', style: _dataBold)),
Expanded(
flex: 2,
child: Text(item['pos_code'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(item['email'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(item['mobile'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(item['bank_name'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(item['aadhar'] ?? '-', style: _dataBold),
),
Expanded(flex: 2, child: Text(item['pan'] ?? '-', style: _dataBold)),
// Expanded(
// flex: 1,
// child: Row(
// children: [
// Container(
// // color: Colors.yellow.shade50,
// child: Transform.scale(
// scale: 0.4, // reduce size (0.70.9 works well)
// child: Switch(
// value: item['is_active'] == "1",
// onChanged: (val) {
// setState(() {
// item['is_active'] = val ? "1" : "0";
// });
// final response = apiService.updateStatus(
// item['id'],
// val ? "0" : "1",
// 'pos',
// );
// print("Response - $response");
// },
// activeColor: Color(0xFF2E7D6E), // thumb when active
// activeTrackColor: Color(0xFFDCFCE7), // track when active
// // activeColor: Color(0xFF425B5B), // thumb when active
// // activeTrackColor: Color(0xFFB2D8D3), // track when active
// inactiveThumbColor:
// Colors.grey.shade400, // thumb when inactive
// inactiveTrackColor:
// Colors.grey.shade300, // track when inactive
// ),
// ),
// ),
// ],
// ),
// ),
//
// Expanded(
// flex: 1,
// child: item['is_active'] == "1"
// ? Row(
// children: [
// // GestureDetector(
// // onTap: () {
// // // Navigator.pop(context);
// // print('EDITStaff - ${item['id']}');
// // dynamic id = item['id'];
// // context.go('/pos/$id');
// // },
// // child: Image.asset(
// // "assets/miscellaneous/Edit.png",
// // height: 15,
// // width: 15,
// // ),
// // ),
// Tooltip(
// message: 'Edit',
// child: IconButton(
// icon: Image.asset(
// "assets/miscellaneous/Edit.png",
// height: 12,
// width: 15,
// ),
// onPressed: () {
// print('EDITStaff - ${item['id']}');
// dynamic id = item['id'];
// context.go('/pos/$id');
// },
// splashRadius: 28,
// hoverColor: Colors.black12,
// padding: const EdgeInsets.all(8),
// constraints: const BoxConstraints(),
// ),
// ),
// ],
// )
// : Row(
// children: [
// Image.asset(
// "assets/miscellaneous/Edit_muted.png",
// height: 12,
// width: 15,
// ),
// ],
// ),
// ),
],
),
);
}
static final _dataBold = GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _dataSub = GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w300,
color: Color(0xFF585757),
);
static final _headerStyle = GoogleFonts.poppins(
fontSize: 11.2,
fontWeight: FontWeight.w500,
color: Color(0xFF1E293B),
);
}

View File

@ -240,7 +240,10 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
),
const SizedBox(height: 26),
if (profileData?['mobile'] != null ||
profileData?['mobile'] != '') ...[
profileData!['mobile']
.toString()
.trim()
.isNotEmpty) ...[
Row(
children: [
const Icon(
@ -258,7 +261,10 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
const SizedBox(height: 8),
],
if (profileData?['email'] != null ||
profileData?['email'] != '') ...[
profileData!['email']
.toString()
.trim()
.isNotEmpty) ...[
Row(
children: [
const Icon(
@ -276,8 +282,11 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
const SizedBox(height: 16),
],
if (profileData?['address'] != null ||
profileData?['address'] != '') ...[
if (profileData?['address'] != null &&
profileData!['address']
.toString()
.trim()
.isNotEmpty) ...[
if ((roleId != 1) &&
(roleId != 2) &&
(roleId != 3)) ...[
@ -304,9 +313,7 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
],
],
if ((roleId != 1) &&
(roleId != 2) &&
(roleId != 3)) ...[
if ((roleId == 5)) ...[
// Staff, Handler doesn't have this part Only Manager and agent have
Container(
padding: const EdgeInsets.all(6),

View File

@ -27,7 +27,8 @@ import '../../../themes/indicators/input_field_decoration.dart';
class SalesExecutive extends ConsumerStatefulWidget {
final String? id;
const SalesExecutive({super.key, this.id});
final void Function(String value) onSubmit;
const SalesExecutive({super.key, this.id, required this.onSubmit});
@override
ConsumerState<SalesExecutive> createState() => SalesExecutiveState();
}
@ -75,7 +76,7 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
// "emp_id": controllers["code"]?.text,
"is_active": isActive,
// "handler_id": selectedHandler,
"manager_id": userId,
"manager_id": managerId,
};
return data;
}
@ -204,7 +205,9 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
context,
'SalesExecutive Created Successfully',
);
context.go(AppRoutes.salesExecutiveLst);
Navigator.of(context).pop();
widget.onSubmit("success");
// context.go(AppRoutes.salesExecutiveLst);
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
@ -310,11 +313,12 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
@override
Widget build(BuildContext context) {
return MainLayout(
title: "SalesExecutive",
body: Container(
return AlertDialog(
backgroundColor: Colors.white,
content: Container(
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width * 0.42,
height: MediaQuery.of(context).size.height * 0.55,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
@ -331,23 +335,23 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Tooltip(
message: 'Back',
child: IconButton(
icon: const Icon(
Icons.arrow_left_sharp,
size: 25,
color: Color(0xFF425B5B),
),
onPressed: () {
context.go(AppRoutes.salesExecutiveLst);
},
splashRadius: 28,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
),
),
// Tooltip(
// message: 'Back',
// child: IconButton(
// icon: const Icon(
// Icons.arrow_left_sharp,
// size: 25,
// color: Color(0xFF425B5B),
// ),
// onPressed: () {
// context.go(AppRoutes.salesExecutiveLst);
// },
// splashRadius: 28,
// hoverColor: Colors.black12,
// padding: const EdgeInsets.all(8),
// constraints: const BoxConstraints(),
// ),
// ),
const SizedBox(width: 5), // spacing between icon and text
Text(
"Sales Executive",
@ -356,6 +360,21 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
fontWeight: FontWeight.w500,
),
),
Spacer(),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: const Color(0xFFF1F1F1),
borderRadius: BorderRadius.circular(5.0),
),
child: Tooltip(
message: 'Close',
child: const Icon(Icons.close, size: 18),
),
),
),
],
),
),
@ -405,7 +424,7 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
),
],
),
SizedBox(height: 30),
// SizedBox(height: 30),
],
),
),
@ -422,51 +441,40 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Expanded(child: buildName()),
SizedBox(width: 25),
Expanded(child: buildEmail()),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(child: buildPhNumber()),
SizedBox(width: 25),
Expanded(child: buildAddress()),
],
),
Row(children: [buildName(), SizedBox(width: 25), buildEmail()]),
SizedBox(height: 20),
Row(children: [buildPhNumber(), SizedBox(width: 25), buildAddress()]),
],
),
);
}
Widget buildName() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Name *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
controller: controllers['name']!,
validator: (value) => Validators.requiredField(value, "name"),
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildEmail() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Email *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['email']!,
borderColor: Color(0xFFE2E8F0),
@ -485,18 +493,18 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildPhNumber() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Phone Number *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['mobile']!,
borderColor: Color(0xFFE2E8F0),
@ -516,18 +524,18 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildAddress() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Address", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['address']!,
borderColor: Color(0xFFE2E8F0),
@ -535,15 +543,15 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[ a-zA-Z0-9_#/.]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildId() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("SalesExecutive Id ", style: _textStyle),
SizedBox(width: 10),
@ -552,7 +560,7 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
// validator: (value) => Validators.requiredField(value, "id"),
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
@ -567,7 +575,7 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
// ThemedFormField(
// controller: controllers['address']!,
//
// txtwidth: MediaQuery.of(context).size.width * 0.26,
// txtwidth: MediaQuery.of(context).size.width *0.18,
// ),
// ],
// );
@ -590,8 +598,8 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
final isReadOnly = widget.id != null;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Role *", style: _textStyle),
SizedBox(width: 10),
@ -602,7 +610,7 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
: MediaQuery.of(context).size.width * 0.18,
// height: 40,
child: AbsorbPointer(
absorbing: isReadOnly,
@ -727,8 +735,8 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
// final isReadOnly = widget.id != null;
final isReadOnly = false;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Handler Name *", style: _textStyle),
SizedBox(width: 10),
@ -739,7 +747,7 @@ class SalesExecutiveState extends ConsumerState<SalesExecutive> {
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
: MediaQuery.of(context).size.width * 0.18,
// height: 40,
child: AbsorbPointer(
absorbing: isReadOnly,

View File

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import 'package:nhance_partner/presentation/screens/UserManagement/SalesExecutive/salesExecutive.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
@ -56,6 +57,12 @@ class SalesExecutiveListState extends ConsumerState<SalesExecutiveList> {
// getSalesExecutiveList();
}
void refresh() {
if (prefid != null && role != null) {
getSalesExecutiveList(prefid, role);
}
}
List<dynamic> get _paginatedData {
// Sort descending by id first
final sortedData = [...filteredData]
@ -131,6 +138,20 @@ class SalesExecutiveListState extends ConsumerState<SalesExecutiveList> {
}
}
Future<void> showSalesExecutive({required String id}) {
print('showSalesEXEC : $id');
return showDialog(
context: context,
builder: (ctx) => SalesExecutive(
id: id,
onSubmit: (value) {
debugPrint("New onSubmit: $value");
refresh();
},
),
);
}
@override
Widget build(BuildContext context) {
managerId = ref.watch(managerIdProvider);
@ -170,7 +191,7 @@ class SalesExecutiveListState extends ConsumerState<SalesExecutiveList> {
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Sales Executive List',
'Sales Executive',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
@ -216,7 +237,8 @@ class SalesExecutiveListState extends ConsumerState<SalesExecutiveList> {
SizedBox(width: 10),
InkWell(
onTap: () {
context.go('/salesExecutive/create');
showSalesExecutive(id: 'create');
// context.go('/salesExecutive/create');
},
child: Container(
padding: EdgeInsets.all(4.8),
@ -427,7 +449,9 @@ class SalesExecutiveListState extends ConsumerState<SalesExecutiveList> {
onPressed: () {
print('EDITStaff - ${item['id']}');
dynamic id = item['id'];
context.go('/salesExecutive/$id');
// context.go('/salesExecutive/$id');
showSalesExecutive(id: id);
},
splashRadius: 28,
hoverColor: Colors.black12,

View File

@ -27,7 +27,8 @@ import '../../../themes/indicators/input_field_decoration.dart';
class Staff extends ConsumerStatefulWidget {
final String? id;
const Staff({super.key, this.id});
final void Function(String value) onSubmit;
const Staff({super.key, this.id, required this.onSubmit});
@override
ConsumerState<Staff> createState() => StaffState();
}
@ -56,8 +57,8 @@ class StaffState extends ConsumerState<Staff> {
List<Map<String, dynamic>> filteredRolesData = [];
List<Map<String, dynamic>> getRolesData = [];
List<dynamic>? selectedHandlerIds = [];
String? selectedHandler;
List<dynamic>? selectedHandlerIds = [];
List<Map<String, dynamic>> filteredHandlersData = [];
List<Map<String, dynamic>> getHandlersData = [];
@ -109,7 +110,7 @@ class StaffState extends ConsumerState<Staff> {
}
void updateData() async {
if (widget.id != null && widget.id != 'create') {
if (widget.id != null && widget.id != 'Create') {
dynamic response = await apiService.findSingleStaffData(widget.id!);
final data = response['data'];
print("updateData - ${response['data']}");
@ -184,7 +185,7 @@ class StaffState extends ConsumerState<Staff> {
}
Future<void> createUserData(data) async {
final bool isUpdating = widget.id != null && widget.id != 'create';
final bool isUpdating = widget.id != null && widget.id != 'Create';
final String? id = isUpdating ? widget.id : null;
final String apiUrldata;
// apiUrldata = isUpdating
@ -234,7 +235,10 @@ class StaffState extends ConsumerState<Staff> {
context,
'Staff Created Successfully',
);
context.go(AppRoutes.staffLst);
Navigator.of(context).pop();
widget.onSubmit("success");
// context.go(AppRoutes.staffLst);
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
@ -340,11 +344,12 @@ class StaffState extends ConsumerState<Staff> {
@override
Widget build(BuildContext context) {
return MainLayout(
title: "Staff",
body: Container(
return AlertDialog(
backgroundColor: Colors.white,
content: Container(
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width,
width: MediaQuery.of(context).size.width * 0.62,
height: MediaQuery.of(context).size.height * 0.65,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
@ -361,23 +366,23 @@ class StaffState extends ConsumerState<Staff> {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Tooltip(
message: 'Back',
child: IconButton(
icon: const Icon(
Icons.arrow_left_sharp,
size: 25,
color: Color(0xFF425B5B),
),
onPressed: () {
context.go(AppRoutes.staffLst);
},
splashRadius: 28,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
),
),
// Tooltip(
// message: 'Back',
// child: IconButton(
// icon: const Icon(
// Icons.arrow_left_sharp,
// size: 25,
// color: Color(0xFF425B5B),
// ),
// onPressed: () {
// context.go(AppRoutes.staffLst);
// },
// splashRadius: 28,
// hoverColor: Colors.black12,
// padding: const EdgeInsets.all(8),
// constraints: const BoxConstraints(),
// ),
// ),
const SizedBox(width: 5), // spacing between icon and text
Text(
"Staff",
@ -386,6 +391,21 @@ class StaffState extends ConsumerState<Staff> {
fontWeight: FontWeight.w500,
),
),
Spacer(),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: const Color(0xFFF1F1F1),
borderRadius: BorderRadius.circular(5.0),
),
child: Tooltip(
message: 'Close',
child: const Icon(Icons.close, size: 18),
),
),
),
],
),
),
@ -455,30 +475,30 @@ class StaffState extends ConsumerState<Staff> {
children: [
Row(
children: [
Expanded(child: buildName()),
buildName(),
SizedBox(width: 25),
Expanded(child: buildEmail()),
buildEmail(),
SizedBox(width: 25),
buildPhNumber(),
],
),
SizedBox(height: 20),
Row(
children: [
Expanded(child: buildPhNumber()),
buildRole(context),
SizedBox(width: 25),
Expanded(child: buildRole(context)),
if (showHandler) ...[
Row(
children: [
buildHandler(context),
SizedBox(width: 25),
SizedBox.shrink(),
],
),
],
],
),
SizedBox(height: 20),
if (showHandler) ...[
Row(
children: [
Expanded(child: buildHandler(context)),
SizedBox(width: 25),
Expanded(child: SizedBox.shrink()),
],
),
],
// Row(
// children: [
// Expanded(child: buildAddress()),
@ -492,28 +512,28 @@ class StaffState extends ConsumerState<Staff> {
}
Widget buildName() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Full Name *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
controller: controllers['name']!,
validator: (value) => Validators.requiredField(value, "name"),
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildEmail() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Email *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['email']!,
borderColor: Color(0xFFE2E8F0),
@ -532,18 +552,18 @@ class StaffState extends ConsumerState<Staff> {
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_@.]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildPhNumber() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Phone Number *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['mobile']!,
borderColor: Color(0xFFE2E8F0),
@ -563,24 +583,24 @@ class StaffState extends ConsumerState<Staff> {
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
}
Widget buildId() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Staff Id ", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['code']!,
borderColor: Color(0xFFE2E8F0),
highlightColor: Color(0xFF50A398),
// validator: (value) => Validators.requiredField(value, "id"),
txtwidth: MediaQuery.of(context).size.width * 0.26,
txtwidth: MediaQuery.of(context).size.width * 0.18,
),
],
);
@ -595,7 +615,7 @@ class StaffState extends ConsumerState<Staff> {
// ThemedFormField(
// controller: controllers['address']!,
//
// txtwidth: MediaQuery.of(context).size.width * 0.26,
// txtwidth: MediaQuery.of(context).size.width * 0.18,
// ),
// ],
// );
@ -616,13 +636,13 @@ class StaffState extends ConsumerState<Staff> {
showHandler = false;
}
final isReadOnly = widget.id != null;
final isReadOnly = widget.id != null && widget.id != 'Create';
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Role *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
// color: Colors.white,
@ -630,7 +650,7 @@ class StaffState extends ConsumerState<Staff> {
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
: MediaQuery.of(context).size.width * 0.18,
// height: 40,
child: AbsorbPointer(
absorbing: isReadOnly,
@ -719,13 +739,14 @@ class StaffState extends ConsumerState<Staff> {
// constraints: BoxConstraints(),
),
onChanged: widget.id != null
onChanged: widget.id != null && widget.id != 'Create'
? null
: (val) {
if (val != null) {
print("Selected Role : ${val['role']}");
print("Id: ${val['id']}");
selectedRole = val['id'];
print('selectedRole - ${val['role']}');
if (val['role'] == 'Staff') {
setState(() {
showHandler = true;
@ -736,6 +757,8 @@ class StaffState extends ConsumerState<Staff> {
});
}
print('showHandler - $showHandler');
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
@ -755,11 +778,11 @@ class StaffState extends ConsumerState<Staff> {
// final isReadOnly = widget.id != null;
final isReadOnly = false;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Handler Name *", style: _textStyle),
SizedBox(width: 10),
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
// color: Colors.white,
@ -767,7 +790,7 @@ class StaffState extends ConsumerState<Staff> {
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
: MediaQuery.of(context).size.width * 0.18,
// height: 40,
child: AbsorbPointer(
absorbing: isReadOnly,

View File

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import 'package:nhance_partner/presentation/screens/UserManagement/Staff/staff.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
@ -56,6 +57,13 @@ class StaffListState extends ConsumerState<StaffList> {
});
// getStaffList();
}
void refresh() {
if (prefid != null && role != null) {
getStaffList(prefid, role);
}
}
//
// @override
// void didChangeDependencies() {
@ -167,6 +175,20 @@ class StaffListState extends ConsumerState<StaffList> {
}
}
Future<void> showStaff({required String id}) {
print('showAgent : $id');
return showDialog(
context: context,
builder: (ctx) => Staff(
id: id,
onSubmit: (value) {
debugPrint("New Claims: $value");
refresh();
},
),
);
}
List<Widget> _buildPopupMenuActions(BuildContext context, dynamic data) {
return [
GestureDetector(
@ -204,48 +226,6 @@ class StaffListState extends ConsumerState<StaffList> {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Container(
// // height: 30,
// // color: Colors.red.shade50,
// width: MediaQuery.of(context).size.width,
// child: GestureDetector(
// onTap: () {
// context.go(AppRoutes.dashboard);
// },
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Text('Staff List', style: _headerStyle),
// // Icon(
// // Icons.arrow_left_sharp,
// // size: 35,
// // color: Color(0xFF425B5B),
// // ),
// // Tooltip(
// // message: 'Back',
// // child: IconButton(
// // icon: const Icon(
// // Icons.arrow_left_sharp,
// // size: 25,
// // color: Color(0xFF425B5B),
// // ),
// // onPressed: () {
// // context.go(AppRoutes.dashboard);
// // },
// // splashRadius: 18,
// // hoverColor: Colors.black12,
// // padding: const EdgeInsets.all(4),
// // constraints: const BoxConstraints(),
// // ),
// // ),
// // const SizedBox(width: 15), // spacing between icon and text
// ],
// ),
// ),
// ),
//
// SizedBox(height: 5),
Expanded(
child: Container(
// color: Colors.green,
@ -269,7 +249,7 @@ class StaffListState extends ConsumerState<StaffList> {
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'Staff List',
'Staff',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
@ -315,7 +295,9 @@ class StaffListState extends ConsumerState<StaffList> {
SizedBox(width: 10),
InkWell(
onTap: () {
context.go('/staff/create');
// context.go('/staff/create');
showStaff(id: 'Create');
},
child: Container(
padding: EdgeInsets.all(4.8),
@ -544,7 +526,9 @@ class StaffListState extends ConsumerState<StaffList> {
onPressed: () {
print('EDITStaff - ${item['id']}');
dynamic id = item['id'];
context.go('/staff/$id');
showStaff(id: id);
// context.go('/staff/$id');
},
splashRadius: 28,
hoverColor: Colors.black12,

View File

@ -243,6 +243,12 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
}).toList();
}
List<String> buildBrokerShortNames() {
return filteredBrokerWise.map<String>((item) {
return item['broker_short_name']?.toString() ?? '';
}).toList();
}
List<BarData> buildProductBarData() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
@ -291,8 +297,9 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
children: [
Spacer(),
Text('Hide and Show Charts : ', style: _styleSmall1),
Row(
@ -355,19 +362,43 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
),
],
),
Spacer(),
legendWidget(),
],
),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
showInsurer ? Expanded(child: insurerContainer()) : SizedBox(),
showBroker ? Expanded(child: brokerContainer()) : SizedBox(),
showProduct ? Expanded(child: productContainer()) : SizedBox(),
],
(showInsurer || showBroker)
? Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// if (showInsurer) Expanded(child: insurerContainer()),
// if (showBroker) Expanded(child: brokerContainer()),
showInsurer
? Expanded(child: insurerContainer())
: SizedBox(),
showBroker
? Expanded(child: brokerContainer())
: SizedBox(),
// showProduct ? Expanded(child: productContainer()) : SizedBox(),
],
),
)
: SizedBox(),
if (showProduct) ...[
SizedBox(height: 10),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
showProduct
? Expanded(child: productContainer())
: SizedBox(),
],
),
),
),
] else ...[
SizedBox.shrink(),
],
],
),
);
@ -381,31 +412,18 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
return Card(
margin: const EdgeInsets.only(right: 10),
color: Colors.white,
elevation: 1, // card shadow
// color: Colors.amber.shade100,
elevation: 0.01, // card shadow
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Insurer Wise', style: _chartHeader),
Text(
'(Previous / Current Month)',
style: _chartHeader.copyWith(
color: Colors.black87,
fontSize: 8,
),
),
],
),
),
Expanded(child: Text('Insurer-Wise', style: _chartHeader)),
// buildInsurer(context),
Text(
@ -425,6 +443,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
SizedBox(height: 10),
Expanded(
child: Container(
// color: Colors.amber.shade100,
clipBehavior: Clip.none,
// color: Colors.red.shade100,
child: CustomBarChart(
@ -433,7 +452,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
),
),
),
legendWidget(),
// legendWidget(),
],
),
),
@ -443,27 +462,23 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
Widget brokerContainer() {
final chartBrokerData = buildBrokerBarData();
final chartBrokerLabels = buildBrokerLabels();
final chartBrokerShortName = buildBrokerShortNames();
print('chartBrokerData - $chartBrokerData');
print('chartBrokerLabels - $chartBrokerLabels');
return Card(
margin: const EdgeInsets.only(right: 10),
color: Colors.white,
elevation: 1, // card shadow
elevation: 0.01, // card shadow
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'Broker Wise (Previous / Current Month)',
style: _chartHeader,
),
),
Expanded(child: Text('Broker-Wise', style: _chartHeader)),
Text(
isBrokerWisePolicy ? "Policies" : "Premium",
@ -483,10 +498,14 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
Expanded(
child: CustomBarChart(
dataList: chartBrokerData,
labels: chartBrokerLabels,
labels: chartBrokerShortName,
shortName: chartBrokerLabels,
// labels: chartBrokerLabels,
// shortName: chartBrokerShortName,
),
),
legendWidget(),
// legendWidget(),
],
),
),
@ -501,22 +520,17 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
return Card(
margin: const EdgeInsets.only(right: 10),
color: Colors.white,
elevation: 1, // card shadow
elevation: 0.01, // card shadow
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
clipBehavior: Clip.antiAlias,
child: Padding(
padding: const EdgeInsets.all(10.0),
padding: const EdgeInsets.all(4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'Product Wise (Previous / Current Month)',
style: _chartHeader,
),
),
Expanded(child: Text('Product-Wise', style: _chartHeader)),
Text(
isProductWisePolicy ? "Policies" : "Premium",
style: _styleSmall1,
@ -538,7 +552,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
labels: chartProductLabels,
),
),
legendWidget(),
// legendWidget(),
],
),
),

View File

@ -372,6 +372,8 @@ class _DashboardState extends ConsumerState<PartnerDashboard> {
: agentsPerformanceListPremium,
isPerfomingAgntPolicy:
isPerfomingAgntPolicy,
apiService: apiService,
managerId: managerId,
),
),
],
@ -437,6 +439,8 @@ class _DashboardState extends ConsumerState<PartnerDashboard> {
: nonAgentsPerformanceListPremium,
isPerfomingAgntPolicy:
isNonPerfomingAgntPolicy,
apiService: apiService,
managerId: managerId,
),
),
],
@ -459,12 +463,16 @@ class Performance extends StatefulWidget {
final String title;
final List<Map<String, dynamic>> data;
final bool isPerfomingAgntPolicy;
late ApiService apiService;
dynamic managerId;
Performance({
Key? key,
required this.title,
required this.data,
required this.isPerfomingAgntPolicy,
required this.apiService,
required this.managerId,
}) : super(key: key);
@override
@ -504,7 +512,7 @@ class _PerformanceState extends State<Performance> {
Expanded(
flex: 1,
child: Text(
"S.no.",
"S.No.",
textAlign: TextAlign.left,
style: _headerStyle,
),
@ -521,7 +529,7 @@ class _PerformanceState extends State<Performance> {
flex: 1,
child: Text(
"Partner Code",
textAlign: TextAlign.center,
textAlign: TextAlign.left,
style: _headerStyle,
),
),
@ -610,34 +618,45 @@ class _PerformanceState extends State<Performance> {
flex: 1,
child: Text(
row['total_premium_amount'] ?? "",
textAlign: TextAlign.center,
textAlign: TextAlign.right,
style: _tableDataStyle,
),
),
],
// (widget.title == 'Performing Partner')
// ? Expanded(
// flex: 1,
// child: Text(
// isPerfomingAgntPolicy
// ? (row['policy_count']?.toString() ?? "0")
// : (row['total_premium_amount']
// ?.toString() ??
// "0"),
// textAlign: TextAlign.center,
// style: GoogleFonts.inter(
// fontSize: 11,
// fontWeight: FontWeight.w500,
// // color: isPerfomingAgntPolicy
// // ? Colors.black
// // : (row['status']?.toString().toLowerCase() ==
// // "active"
// // ? Colors.green
// // : Colors.red),
// ),
// ),
// )
// : SizedBox(),
SizedBox(width: 10),
InkWell(
onTap: () async {
final path1 = 'PerformingTop50';
final path2 = widget.isPerfomingAgntPolicy
? "NoBusiness"
: "NonPerformingBelow50K";
final path = (widget.title == 'Performing Partner')
? path1
: path2;
final id =
((widget.title == 'Performing Partner') &&
(row['agent_id'] != null))
? row['agent_id']
: '';
final month = 'current';
await widget.apiService.generateChartExcel(
path,
id,
month,
widget.managerId,
);
},
child: Tooltip(
message: 'Click To Download Excel',
child: Icon(
Icons.download,
size: 14,
color: Colors.green.shade300,
),
),
),
],
),
);

View File

@ -48,6 +48,7 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
[]; // 👈 Add this at the top with other state variables
Map<String, Color> vehicleColors = {};
List<String> staffLabels = [];
List<String> staffLabelsId = [];
// List<List<List<dynamic>>> staffChartData = [];
List<List<List<dynamic>>> staffChartData = [];
@ -170,6 +171,7 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
print('Dynamic Vehicle Colors: $dynamicVehicleColors');
List<String> newLabels = [];
List<String> newLabelsId = [];
List<List<List<dynamic>>> newChartData = [];
List<List<String>> vehicleNames = [];
double newMax = 0.0;
@ -188,6 +190,9 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
'';
newLabels.add(name);
final id = (staff['sales_executive_id'])?.toString() ?? '';
newLabelsId.add(id);
final products = (staff['products_list'] ?? []) as List<dynamic>;
List<List<dynamic>> currentRod = [];
@ -232,6 +237,7 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
filteredStaffWise = List<Map<String, dynamic>>.from(originalStaffWise);
staffLabels = newLabels;
staffLabelsId = newLabelsId;
staffChartData = newChartData;
staffVehicleTypeNames = vehicleNames;
vehicleColors = dynamicVehicleColors; // Save for legend
@ -274,79 +280,6 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
}).toList();
}
// In insurerContainer() you are passing staffLabels (which we now populate).
// Also set the chart maxY to some headroom:
// CustomStackedBarChart(..., maxY: (maxPremium * 1.1).ceilToDouble(), ...)
List<BarData> buildStaffBarData() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0.0;
return 0.0;
}
return filteredStaffWise.map((item) {
print('policies_current - ${item['total_policies_current_month']}');
print('policies_PREV - ${item['total_policies_pre_month']}');
print('premium_current - ${item['total_premium_current_month']}');
print('premium_PREV - ${item['total_premium_pre_month']}');
return BarData(
Colors.blue,
isInsureWisePolicy
? parseDouble(item['total_policies_current_month'])
: parseDouble(item['total_premium_current_month']),
isInsureWisePolicy
? parseDouble(item['total_policies_pre_month'])
: parseDouble(item['total_premium_pre_month']),
item['vehicle_type'] != null ? item['vehicle_type'] : 0,
'Productivity',
5,
);
}).toList();
}
List<BarData> buildProductBarData() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0.0;
return 0.0;
}
return filteredProductWise.map((item) {
print('policies_current - ${item['total_policies_current_month']}');
print('policies_PREV - ${item['total_policies_pre_month']}');
print('premium_current - ${item['total_premium_current_month']}');
print('premium_PREV - ${item['total_premium_pre_month']}');
return BarData(
Colors.blue,
isProductWisePolicy
? parseDouble(item['total_policies_current_month'])
: parseDouble(item['total_premium_current_month']),
isProductWisePolicy
? parseDouble(item['total_policies_pre_month'])
: parseDouble(item['total_premium_pre_month']),
item['vehicle_type'],
'Productivity',
10,
);
}).toList();
}
List<String> buildProductLabels() {
return filteredProductWise.map<String>((item) {
final name = item['vehicle_type'];
if (name != null && name.toString().trim().isNotEmpty) {
return name.toString();
} else {
return item['vehicle_type']?.toString() ?? '';
}
}).toList();
}
@override
Widget build(BuildContext context) {
return Container(
@ -417,9 +350,6 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
}
Widget insurerContainer() {
final chartStaffData = buildStaffBarData();
final chartStaffLabels = buildStaffLabels();
return Card(
margin: const EdgeInsets.only(right: 10),
color: Colors.white,
@ -447,6 +377,10 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
Transform.scale(
scale: 0.5,
child: Switch(
activeColor: Colors.teal,
activeTrackColor: Colors.grey.shade200,
inactiveThumbColor: Colors.orange,
inactiveTrackColor: Colors.white,
value: isInsureWisePolicy,
onChanged: (val) {
setState(() {
@ -463,6 +397,7 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
Expanded(
child: CustomStackedBarChart(
labels: staffLabels,
labelsId: staffLabelsId,
// maxY: (maxPremium * 1.1).ceilToDouble(),
maxY: _getSmartMaxValue(),
initialRotation: 1,
@ -479,55 +414,6 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
);
}
// Widget productContainer() {
// final chartProductData = buildProductBarData();
// final chartProductLabels = buildProductLabels();
// print('chartProductData - $chartProductData');
// print('chartProductLabels - $chartProductLabels');
// return Container(
// padding: EdgeInsets.all(10),
// margin: EdgeInsets.only(right: 20),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(10),
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Row(
// children: [
// Expanded(
// child: Text(
// 'Sales Executive Product Wise (Previous / Current Month)',
// style: _chartHeader,
// ),
// ),
// Text(
// isProductWisePolicy ? "Policies" : "Premium",
// style: _styleSmall1,
// ),
// Transform.scale(
// scale: 0.5,
// child: Switch(
// value: isProductWisePolicy,
// onChanged: (val) => setState(() => isProductWisePolicy = val),
// ),
// ),
// ],
// ),
// SizedBox(height: 10),
// Expanded(
// child: CustomBarChart(
// dataList: chartProductData,
// labels: chartProductLabels,
// ),
// ),
// legendWidget(),
// ],
// ),
// );
// }
Widget legendWidget() {
return Wrap(
spacing: 8,

View File

@ -344,7 +344,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
if (userRole == 'staff') {
context.go(AppRoutes.enquiryForStaff);
} else if (userRole == 'Accounts') {
context.go(AppRoutes.payout);
// context.go(AppRoutes.payout);
context.go(AppRoutes.invoiceList);
} else {
context.go(AppRoutes.dashboard);
}

View File

@ -0,0 +1,806 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import '../../../core/services/api_service.dart';
import '../../layouts/responsive_layout.dart';
import '../../providers/manager_provider.dart';
import '../../providers/userRoleProvider.dart';
import '../../themes/indicators/date_field_theme.dart';
import '../../themes/indicators/input_field_decoration.dart';
class DateFilterRowPayout extends ConsumerStatefulWidget {
final TextEditingController startController;
final String? selectedBroker;
final List<dynamic>? selectedParnter;
final String? selectedStaffId;
final TextEditingController endController;
final ValueChanged<String?>? onBrokerChanged;
final ValueChanged<List<String>>? onPartnerChanges;
final String? dataFrom;
final VoidCallback onFilter;
final VoidCallback onRefresh;
final GlobalKey<FormState> formKey;
final bool isMobile;
final role;
final id;
const DateFilterRowPayout({
super.key,
required this.startController,
required this.endController,
required this.onFilter,
required this.onRefresh,
this.onBrokerChanged,
this.onPartnerChanges,
required this.selectedStaffId,
required this.selectedParnter,
this.selectedBroker,
required this.formKey,
required this.role,
required this.id,
this.isMobile = false,
this.dataFrom,
});
@override
ConsumerState<DateFilterRowPayout> createState() => _DateFilterRowState();
}
class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyBroker =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownKeyPartner = GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
late ApiService apiService;
List<dynamic>? selectedPartnerIds = [];
List<Map<String, dynamic>> getPartnerData = [];
List<Map<String, dynamic>> filteredPartnerData = [];
List<Map<String, dynamic>> getBrokerData = [];
List<Map<String, dynamic>> filteredBrokerData = [];
String? selectedStaff;
String? selectedBroker;
String? selectedPartner;
String? selectedStaffName;
dynamic role;
dynamic managerId;
bool isLoading = false;
bool isLoadingBroker = false;
bool isLoadingAgentList = false;
@override
void initState() {
super.initState();
apiService = ApiService();
Future.microtask(() {
managerId = ref.watch(managerIdProvider);
final userID = ref.watch(userIdProvider);
role = ref.watch(userRoleProvider);
print("managerId - $managerId");
if (userID != null && role != null) {
print('hansles');
getPartnerDetails(userID);
}
if (managerId != null) {
print('managerId - $managerId');
getAgentList(managerId);
}
});
getBroker();
}
Future<void> getBroker() async {
print('getBroker called');
setState(() {
isLoadingBroker = true;
});
try {
final response = await apiService.fetchMasterDropDown('Broker');
if (response['status'] == 200) {
print('getBroker - ${response['data']}');
setState(() {
getBrokerData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getBrokerData');
filteredBrokerData = List.from(getBrokerData);
print('originalData - $filteredBrokerData');
});
} else {
getBrokerData = [];
filteredBrokerData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoadingBroker = false;
});
}
}
Future<void> getAgentList(id) async {
print('getAgentListData called');
setState(() {
isLoadingAgentList = true;
});
try {
final response = await apiService.fetchAgentNameDropDown(id);
print('getAgentListData called response');
print('get Agent- ${response['data']}');
if (response['status'] == 'success') {
print('get Agent- ${response['data']}');
setState(() {
getPartnerData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getPartnerData');
filteredPartnerData = List.from(getPartnerData);
print('originalAgentData - $filteredPartnerData');
});
} else {
getPartnerData = [];
filteredPartnerData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoadingAgentList = false;
});
}
}
Future<void> getPartnerDetails(int id) async {
print('getPartnerDetails called By handler');
setState(() {
isLoading = true;
});
try {
// final response = await apiService.fetchStaffUserList(id, role);
final response = await apiService.fetchStaffListForEnquiryAssignDropDown(
managerId,
id,
role,
);
if (response['status'] == 'success') {
print('getStaffDetails - ${response['data']}');
setState(() {
getPartnerData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getPartnerData');
filteredPartnerData = List.from(getPartnerData);
print('originalData - $filteredPartnerData');
});
} else {
getPartnerData = [];
filteredPartnerData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
final spacing = 5.0;
List<Widget> getRowChildren(bool isMobile, double spacing) {
final dateFields = [
buildStartDate(context),
SizedBox(width: spacing),
buildEndDate(context),
SizedBox(width: spacing),
buildBroker(context),
SizedBox(width: spacing),
buildPartner(context),
SizedBox(width: spacing),
];
final buttons = [
Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0),
// child: GestureDetector(
// onTap: () {
// if (formKey.currentState!.validate()) onFilter();
// },
// child: Icon(Icons.filter_alt_outlined),
// ),
child: Tooltip(
message: 'Filter',
child: IconButton(
icon: const Icon(
Icons.search_rounded,
size: 18,
color: const Color(0xFF94A3B8),
),
onPressed: () {
if (widget.formKey.currentState!.validate()) {
widget.onFilter();
}
},
),
),
),
Padding(
padding: const EdgeInsets.all(0.0),
// child: GestureDetector(onTap: onRefresh, child: Icon(Icons.refresh)),
child: Tooltip(
message: 'Refresh',
child: IconButton(
onPressed: widget.onRefresh,
icon: const Icon(
Icons.refresh,
size: 18,
color: const Color(0xFF94A3B8),
),
),
),
),
];
if (isMobile) {
// For mobile: buttons below fields
return [
...dateFields,
SizedBox(height: 10),
Row(mainAxisAlignment: MainAxisAlignment.end, children: buttons),
];
} else {
// For desktop: buttons inline with fields
return [...dateFields, Spacer(), ...buttons];
}
}
return Form(
key: widget.formKey,
child: widget.isMobile
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: getRowChildren(widget.isMobile, spacing),
)
: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: getRowChildren(widget.isMobile, spacing),
),
);
}
Widget buildStartDate(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Start Date', style: _textStyle),
SizedBox(height: 5),
SizedBox(
height: 35,
child: ThemedDateField(
hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.15,
// txtheight: 50,
// backgroundColor: const Color(0xFFECECEC),
// validator: (value) => Validators.requiredField(value, "date"),
controller: widget.startController,
onDateSelected: (date) {
print("Picked Date: $date");
widget.startController.text = DateFormat(
'dd-MM-yyyy',
).format(date);
// controllers['date']?.text = date as String;
Future.microtask(() => widget.onFilter());
},
),
),
],
);
}
Widget buildEndDate(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('End Date', style: _textStyle),
SizedBox(height: 5),
SizedBox(
height: 35,
child: ThemedDateField(
hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.15,
// txtheight: 50,
// backgroundColor: const Color(0xFFECECEC),
validator: (value) {
final fromText = widget.startController.text ?? '';
if (fromText.isNotEmpty) {
final startDate = DateFormat('dd-MM-yyyy').parse(fromText);
if (value == null || value.isEmpty) {
return "End Date is required";
}
final endDate = DateFormat('dd-MM-yyyy').parse(value);
if (endDate.isBefore(startDate)) {
return "End Date cannot be earlier than Start Date";
}
}
return null; // no error
},
controller: widget.endController,
lastDate: DateTime.now(),
onDateSelected: (date) {
print("Picked Date: $date");
widget.endController.text = DateFormat('dd-MM-yyyy').format(date);
Future.microtask(() => widget.onFilter());
// controllers['date']?.text = date as String;
},
),
),
],
);
}
Widget buildBroker(BuildContext context) {
Map<String, dynamic>? selectedBrokers = filteredBrokerData.firstWhere(
(item) => item['id'].toString() == widget.selectedBroker,
orElse: () => {},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Broker', style: _textStyle),
SizedBox(height: 5),
SizedBox(
height: 35,
width: MediaQuery.of(context).size.width * 0.15,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyBroker,
selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null,
items: (filter, infiniteScrollProps) {
return filteredBrokerData;
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
// validator: (val) {
// if (val == null) {
// return "Required"; // error message
// }
// return null;
// },
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 15, // smaller icon
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['name'].toString() : "",
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.black,
// color: Color(0XFF6366F1),
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Broker",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
style: GoogleFonts.inter(fontSize: 11, color: Colors.black),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Search Broker...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected Broker : ${val['name']}");
print("Id: ${val['id']}");
// selectedBroker = val['id'];
widget.onBrokerChanged?.call(val['id'].toString());
// widget.onBrokerChanged?.call(val['id']);
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
Widget buildPartner(BuildContext context) {
final isReadOnly = false;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Referer", style: _textStyle),
SizedBox(height: 5),
SizedBox(
height: 35,
width: MediaQuery.of(context).size.width * 0.42,
child: DropdownSearch<Map<String, dynamic>>.multiSelection(
key: dropDownKeyPartner,
// selectedItem: selectedHandlered.isNotEmpty
// ? selectedHandlered
// : null,
selectedItems: filteredPartnerData
.where(
(item) => (selectedPartnerIds ?? []).contains(item['id']),
)
.toList(),
items: (filter, infiniteScrollProps) {
return filteredPartnerData;
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
// validator: (val) {
// if (val == null) {
// return "Required"; // error message
// }
// return null;
// },
validator: (val) {
if (val == null || val.isEmpty) {
return "Required";
}
return null;
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Partner",
).copyWith(
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupPropsMultiSelection.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
showSearchBox: true,
menuProps: MenuProps(backgroundColor: Colors.white),
searchFieldProps: TextFieldProps(
autofocus: true,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Partner...",
hintStyle: GoogleFonts.inter(
fontSize: 11,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
// color: Colors.blue,
color: Color(0xFFEDF6F5),
width: 1.5,
),
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
),
onChanged: (List<Map<String, dynamic>> selectedVals) {
// selectedPartnerIds = selectedVals
// .map((v) => v['id'].toString())
// .toList();
final ids = selectedVals.map((v) => v['id'].toString()).toList();
print("Selected PArnter IDs: $selectedPartnerIds");
widget.onPartnerChanges?.call(ids);
// widget.onPartnerChanges!(v['id']);
},
),
),
],
);
}
Widget buildSelectStaffMem(BuildContext context) {
Map<String, dynamic>? selectedVehicle;
if (widget.selectedStaffId == null) {
selectedVehicle = null;
} else if (widget.selectedStaffId != null) {
selectedVehicle = filteredPartnerData.firstWhere(
(item) => item['id'] == widget.selectedStaffId,
orElse: () => {}, // empty map
);
if (selectedVehicle.isEmpty) selectedVehicle = null;
} else if (selectedStaff != null) {
selectedVehicle = filteredPartnerData.firstWhere(
(item) => item['id'] == selectedStaff,
orElse: () => {}, // empty map
);
if (selectedVehicle.isEmpty) selectedVehicle = null;
} else {
selectedVehicle = null;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text("Staff Name", style: _textStyle),
// SizedBox(height: 10),
SizedBox(
height: 35,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(color: Color(0xFFE2E8F0)),
// color: Colors.black,
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.13,
// height: 45,
child: DropdownSearch<Map<String, dynamic>>(
// key: dropDownKey,
key: ValueKey(selectedStaff),
// selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
selectedItem: selectedVehicle,
items: (filter, infiniteScrollProps) {
return filteredPartnerData;
},
itemAsString: (val) => val['name'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['name'].toString() : "",
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Staff ",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
contentPadding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 1, // 👈 adjust this to make the field shorter
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Staff ...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(
fontSize: 13,
color: Colors.black,
),
),
);
},
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected Staff : ${val['name']}");
print("Id: ${val['id']}");
// selectedStaffName = val['name'];
// selectedStaff = val['id'];
widget.onBrokerChanged!(val['id']);
// if (widget.onFilterStaff != null)
// widget.onFilterStaff!(val['id']);
// Future.microtask(() => widget.onFilter());
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
),
],
);
}
static final _textStyle = GoogleFonts.poppins(
fontSize: 11,
fontWeight: FontWeight.w500,
);
}

View File

@ -177,13 +177,16 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
(item['invoice_no'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['invoice_amount'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['invoice_amount_indian_format'] ?? '-')
.toLowerCase()
.contains(query.toLowerCase()) ||
(item['broker_name'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['agent_name'] ?? '-').toLowerCase().contains(
(item['pos_name'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['payout_status'] ?? '-').toLowerCase().contains(
query.toLowerCase(),
)
// ||
@ -195,7 +198,7 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
final TextEditingController _searchStaffController = TextEditingController();
String _formatDate(String rawDate) {
String _formatDateTime(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat('dd-MM-yyyy HH:mm').format(dateTime); // 24-hour format
@ -204,61 +207,69 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
}
}
String _formatDate(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat('dd-MM-yyyy').format(dateTime); // 24-hour format
} catch (e) {
return rawDate; // fallback if parsing fails
}
}
@override
Widget build(BuildContext context) {
return MainLayout(
title: "Pay Out",
body: Container(
height: 30,
// height: 30,
// color: Colors.yellow.shade50,
width: MediaQuery.of(context).size.width,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 30,
width: MediaQuery.of(context).size.width,
child: GestureDetector(
onTap: () {
context.go(AppRoutes.dashboard);
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// Tooltip(
// message: 'Back',
// child: IconButton(
// icon: const Icon(
// Icons.arrow_left_sharp,
// size: 25,
// color: Color(0xFF425B5B),
// ),
// onPressed: () {
// context.go(AppRoutes.dashboard);
// },
// splashRadius: 18,
// hoverColor: Colors.black12,
// padding: const EdgeInsets.all(4),
// constraints: const BoxConstraints(),
// ),
// ),
// const SizedBox(width: 5), // spacing between icon and text
Text(
"Invoice",
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
SizedBox(height: 5),
// Container(
// height: 30,
// width: MediaQuery.of(context).size.width,
// child: GestureDetector(
// onTap: () {
// context.go(AppRoutes.dashboard);
// },
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// // Tooltip(
// // message: 'Back',
// // child: IconButton(
// // icon: const Icon(
// // Icons.arrow_left_sharp,
// // size: 25,
// // color: Color(0xFF425B5B),
// // ),
// // onPressed: () {
// // context.go(AppRoutes.dashboard);
// // },
// // splashRadius: 18,
// // hoverColor: Colors.black12,
// // padding: const EdgeInsets.all(4),
// // constraints: const BoxConstraints(),
// // ),
// // ),
// // const SizedBox(width: 5), // spacing between icon and text
// Text(
// "Invoice",
// style: GoogleFonts.inter(
// fontSize: 14,
// fontWeight: FontWeight.w500,
// ),
// ),
// ],
// ),
// ),
// ),
//
// SizedBox(height: 5),
Expanded(
child: Container(
width: MediaQuery.of(context).size.width,
@ -306,71 +317,15 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
// if (!ResponsiveLayout.isMobile(context)) ...[
// DateFilterRow(
// dataFrom: 'Policy',
// role: roleId,
// id: userId,
// onFilterStaff: (val) {
// print('Selected Filterd STAFF Id - $val');
// SelectedStaffId = val;
// },
// selectedStaffId: SelectedStaffId,
// startController: controllers['startDate']!,
// endController: controllers['endDate']!,
// onStatusChanged: (val) {
// SelectedStatus = val; // update parent
// },
// formKey: _formKey,
// isMobile: ResponsiveLayout.isMobile(context),
// onFilter: () {
// // call your filter logic
// filterDateRange();
// },
// onRefresh: () {
// // call your refresh logic
// refrshfilterDateRange();
// },
// ),
//
// // Form(
// // key: _formKey,
// // child: Row(
// // mainAxisAlignment: MainAxisAlignment.start,
// // crossAxisAlignment: CrossAxisAlignment.end,
// // children: [
// // buildStartDate(context),
// // SizedBox(width: 10),
// // buildEndDate(context),
// // SizedBox(width: 10),
// //
// // Padding(
// // padding: const EdgeInsets.symmetric(
// // vertical: 8.0,
// // ),
// // child: GestureDetector(
// // // onTap: filterDateRange,
// // onTap: () {
// // if (_formKey.currentState!.validate()) {
// // filterDateRange(); // only runs if valid
// // }
// // },
// // child: Icon(Icons.filter_alt_outlined),
// // ),
// // ),
// //
// // Padding(
// // padding: const EdgeInsets.all(8.0),
// // child: GestureDetector(
// // onTap: refrshfilterDateRange,
// // child: Icon(Icons.refresh),
// // ),
// // ),
// // ],
// // ),
// // ),
// Spacer(),
// ],
Text(
"Invoice",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
Spacer(),
ThemedSearchField(
hintText: 'Search',
// backgroundColor: Color(0xFFF6F8F8),
@ -383,24 +338,13 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
),
SizedBox(width: 10),
Tooltip(
message: 'Export File',
message: 'Raise Invoice',
child: InkWell(
onTap: () async {
context.go(AppRoutes.payout);
},
// onTap: () {
// print('TAV ExcelExporter');
//
// ExcelExporter.exportToExcel(
// sheetName: sheetName,
// data: data,
// headers: headers,
// fileName: fileName,
// keys: keys!,
// );
// },
child: Container(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(4.0),
decoration: BoxDecoration(
color: const Color(0xFF2E7D6E),
// color: const Color(0xFF425B5B),
@ -414,7 +358,7 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
// height: 25,
// width: 25,
// ),
Icon(Icons.add, color: Colors.white, size: 25),
Icon(Icons.add, color: Colors.white, size: 18),
// Image.asset("assets/miscellaneous/export", height: 15, width: 15),
],
),
@ -428,20 +372,22 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
data: filteredData,
txt: true,
displayHeaders: [
"Updated Date",
"Invoice Date",
"Invoice No",
"Invoice Date",
"Invoice Amount",
"Broker Name",
"Partner Name",
"Broker ",
"POS ",
"Status ",
"Updated Date",
],
keys: [
"updated_at",
"invoice_date",
"invoice_no",
"invoice_amount",
"invoice_amount_indian_format",
"broker_name",
"agent_name",
"pos_name",
"payout_status",
"updated_at",
],
),
],
@ -450,34 +396,32 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
SizedBox(height: 5),
Container(
decoration: BoxDecoration(
color: Color(0xFFEDF6F5),
color: Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
children: [
Expanded(flex: 3, child: Text('Invoice No', style: _headerStyle)),
Expanded(
flex: 3,
flex: 2,
child: Text('Invoice Date', style: _headerStyle),
),
Expanded(
flex: 3,
flex: 2,
child: Text('Invoice Amount', style: _headerStyle),
),
Expanded(
flex: 3,
flex: 2,
child: Text('Broker Name', style: _headerStyle),
),
Expanded(flex: 2, child: Text('POS Name', style: _headerStyle)),
Expanded(flex: 2, child: Text('Status', style: _headerStyle)),
Expanded(
flex: 3,
child: Text('Partner Name', style: _headerStyle),
),
Expanded(
flex: 3,
flex: 2,
child: Text('Updated Date', style: _headerStyle),
),
Expanded(flex: 3, child: Text('Action', style: _headerStyle)),
Expanded(flex: 2, child: Text('Action', style: _headerStyle)),
],
),
),
@ -516,7 +460,7 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
Widget _buildDataRow(Map<String, dynamic> item, sno) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 16),
// margin: const EdgeInsets.only(top: 10),
decoration: BoxDecoration(
color: Colors.white,
@ -533,40 +477,62 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
child: Text(item['invoice_no'] ?? '-', style: _dataBold),
),
Expanded(
flex: 3,
child: Text(item['invoice_date'] ?? '-', style: _dataBold),
flex: 2,
child: Text(
item['invoice_date'] != null
? _formatDate(item['invoice_date'])
: '-',
style: _dataBold,
),
),
Expanded(
flex: 3,
flex: 2,
child: Text(
item['invoice_amount'] ?? '-',
item['invoice_amount_indian_format'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 3,
flex: 2,
child: Text(
item['broker_name'] ?? '-',
item['broker_name'] != null ? item['broker_name'] : '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 3,
flex: 2,
child: Text(
item['agent_name'] ?? '-',
item['pos_name'] != null ? item['pos_name'] : '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 3,
flex: 2,
child: Text(
_formatDate(item['updated_at']) ?? '-',
// item['payout_status'] != null ? item['payout_status'] : '-',
item['payout_status'] == '1'
? 'Pending'
: item['payout_status'] != null
? 'Completed'
: '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
item['updated_at'] != null
? _formatDateTime(item['updated_at'])
: '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
@ -577,27 +543,38 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
Expanded(
flex: 2,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// EDIT ICON
IconButton(
icon: const Icon(Icons.edit, color: Colors.blue, size: 20),
icon: const Icon(
Icons.remove_red_eye_outlined,
color: Colors.blue,
size: 18,
),
onPressed: () {
// TODO: handle edit
context.go(AppRoutes.payout, extra: item);
print("Edit clicked for ${item['invoice_no']}");
},
splashRadius: 18,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
),
const SizedBox(width: 6),
// DELETE ICON
IconButton(
icon: const Icon(Icons.delete, color: Colors.red, size: 20),
icon: const Icon(Icons.delete, color: Colors.red, size: 18),
onPressed: () {
// TODO: handle delete
deleteInvoice(item['id']);
print("Delete clicked for ${item['invoice_no']}");
},
splashRadius: 18,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
),
],
),
@ -608,14 +585,14 @@ class _InvoiceListState extends ConsumerState<InvoiceList> {
}
static final _dataBold = GoogleFonts.inter(
fontSize: 14,
fontSize: 11.5,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _headerStyle = GoogleFonts.inter(
color: Colors.black,
fontWeight: FontWeight.bold,
static final _headerStyle = GoogleFonts.poppins(
fontSize: 11.2,
fontWeight: FontWeight.w500,
color: Color(0xFF1E293B),
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -371,46 +371,62 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
@override
Widget build(BuildContext context) {
return Wrap(
spacing: 12,
runSpacing: 12,
children: [
buildSelectStaffMem(context),
buildAgentName(context, fromHeader: false),
buildInsurer(context),
buildBroker(context),
buildInsuredName(context),
buildVehicleNumber(context),
buildEmail(context),
buildPhNumber(context),
buildRemarks(context),
return Form(
key: _formKey,
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 12,
runSpacing: 12,
children: [
buildSelectStaffMem(context),
buildAgentName(context, fromHeader: false),
buildInsurer(context),
buildBroker(context),
buildInsuredName(context),
buildVehicleNumber(context),
buildEmail(context),
buildPhNumber(context),
buildRemarks(context),
IconButton(
tooltip: 'Save',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
icon: const Icon(
Icons.check,
color: Colors.green,
size: 28, // smaller icon
),
splashRadius: 14, //
onPressed: () => widget.onSave(widget.selectId),
),
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
IconButton(
tooltip: 'Close',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
icon: const Icon(
Icons.close,
color: Colors.indigo,
size: 28, // smaller icon
children: [
IconButton(
tooltip: 'Save',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 14, minHeight: 24),
icon: const Icon(
Icons.check,
color: Colors.green,
size: 20, // smaller icon
),
splashRadius: 14, //
onPressed: () {
if (!_formKey.currentState!.validate()) return;
widget.onSave(widget.selectId);
},
),
IconButton(
tooltip: 'Close',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 14, minHeight: 24),
icon: const Icon(
Icons.close,
color: Colors.indigo,
size: 20, // smaller icon
),
splashRadius: 14, //
onPressed: widget.onClose,
),
],
),
splashRadius: 14, //
onPressed: widget.onClose,
),
],
],
),
);
}
@ -446,12 +462,12 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
validator: (val) {
if (val == null) {
return "Required"; // error message
}
return null;
},
// validator: (val) {
// if (val == null) {
// return "Required"; // error message
// }
// return null;
// },
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
@ -608,6 +624,10 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
hintText: 'Vehicle Number',
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
enableBorderWidth: 0.5,
bordedRad: 5,
errFieldHgt: 10,
errFieldFont: 0,
isdense: true,
inputFormatters: [
UpperCaseTextFormatter(), // 👈 custom formatter for uppercase
@ -662,7 +682,7 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
// errorText: fieldErrors['name'],
borderColor: Color(0XFF6366F1),
isdense: true,
validator: (value) => Validators.requiredField(value, "name"),
// validator: (value) => Validators.requiredField(value, "name"),
widthNone: true,
),
),
@ -678,11 +698,15 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
SizedBox(height: 5),
SizedBox(
width: MediaQuery.of(context).size.width * 0.1,
child: ThemedFormInlineField(
child: ThemedFormField(
controller: widget.controllers['email']!,
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
hintText: 'Email',
bordedRad: 5,
enableBorderWidth: 0.5,
// errFieldHgt: 10,
// errFieldFont: 0,
isdense: true,
validator: (value) => Validators.nonReqemail(value, "email"),
inputFormatters: [
@ -705,15 +729,17 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
SizedBox(height: 5),
SizedBox(
width: MediaQuery.of(context).size.width * 0.1,
child: ThemedFormInlineField(
child: ThemedFormField(
controller: widget.controllers['mobile']!,
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
hintText: 'Mobile',
enableBorderWidth: 0.5,
bordedRad: 5,
isdense: true,
validator: (value) => Validators.nonReqphone(value, "phone"),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[ 0-9]')),
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
txtwidth: ResponsiveLayout.isMobile(context)
? null
@ -737,6 +763,7 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
controller: widget.controllers['remarks']!,
borderColor: Color(0XFF6366F1),
textColr: Color(0XFF6366F1),
bordedRad: 5,
verticalPad: 10,
horizonalPad: 10,
isdense: true,
@ -785,12 +812,12 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
itemAsString: (val) => val['name'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
validator: (val) {
if (val == null) {
return "Required"; // error message
}
return null;
},
// validator: (val) {
// if (val == null) {
// return "Required"; // error message
// }
// return null;
// },
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
@ -963,12 +990,12 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
itemAsString: (val) => val['short_name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
validator: (val) {
if (val == null) {
return "Required"; // error message
}
return null;
},
// validator: (val) {
// if (val == null) {
// return "Required"; // error message
// }
// return null;
// },
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
@ -1117,12 +1144,12 @@ class _SubRowWidgetState extends ConsumerState<SubRowWidget> {
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
validator: (val) {
if (val == null) {
return "Required"; // error message
}
return null;
},
// validator: (val) {
// if (val == null) {
// return "Required"; // error message
// }
// return null;
// },
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(

View File

@ -52,12 +52,14 @@ class EnquiryListStaffInline extends ConsumerStatefulWidget {
class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
int currentPage = 1;
int itemsPerPage = 10;
// int itemsPerPage = 5;
int completedCurrentPage = 1;
// int completedItemsPerPage = 5;
int completedItemsPerPage = 50;
int totalCompletedPages = 1;
List<int> itemsPerPageOptions = [25, 50, 100, 200];
Timer? _debounce;
late ApiService apiService;
dynamic userId;
dynamic idPrimary;
@ -389,21 +391,80 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
}
// Add this getter method to calculate paginated completed data
// List<Map<String, dynamic>> get paginatedCompletedData {
// if (completedData.isEmpty) return [];
//
// // Calculate total pages
// totalCompletedPages = (completedData.length / completedItemsPerPage).ceil();
//
// // Calculate start and end indices
// final startIndex = (completedCurrentPage - 1) * completedItemsPerPage;
// final endIndex = (startIndex + completedItemsPerPage).clamp(
// 0,
// completedData.length,
// );
//
// // Return paginated subset
// return completedData.sublist(startIndex, endIndex);
// }
List<Map<String, dynamic>> get paginatedCompletedData1 {
// If search is active (filteredData is different from completedData), use filteredData
// Otherwise use completedData
final dataToUse = filteredData;
print("paginatedCompletedData - $dataToUse");
if (dataToUse.isEmpty) return [];
// For completed tab only
if (selectedIndex == 1) {
// Calculate total pages based on current data
totalCompletedPages = (dataToUse.length / completedItemsPerPage).ceil();
if (totalCompletedPages == 0) totalCompletedPages = 1;
// Calculate start and end indices
final startIndex = (completedCurrentPage - 1) * completedItemsPerPage;
final endIndex = (startIndex + completedItemsPerPage).clamp(
0,
dataToUse.length,
);
// Return paginated subset
return dataToUse.sublist(startIndex, endIndex);
}
return dataToUse;
}
List<Map<String, dynamic>> get paginatedCompletedData {
if (completedData.isEmpty) return [];
final dataToUse = filteredData;
// Calculate total pages
totalCompletedPages = (completedData.length / completedItemsPerPage).ceil();
if (dataToUse.isEmpty) return [];
// Calculate start and end indices
final startIndex = (completedCurrentPage - 1) * completedItemsPerPage;
final endIndex = (startIndex + completedItemsPerPage).clamp(
0,
completedData.length,
);
if (selectedIndex == 1) {
totalCompletedPages = (dataToUse.length / completedItemsPerPage)
.ceil()
.clamp(1, double.infinity)
.toInt();
// Return paginated subset
return completedData.sublist(startIndex, endIndex);
final startIndex = (completedCurrentPage - 1) * completedItemsPerPage;
// 🚨 Guard: page out of range after search
if (startIndex >= dataToUse.length) {
completedCurrentPage = 1;
return dataToUse.take(completedItemsPerPage).toList();
}
final endIndex = (startIndex + completedItemsPerPage).clamp(
0,
dataToUse.length,
);
return dataToUse.sublist(startIndex, endIndex);
}
return dataToUse;
}
Future<void> autoRefrshfilterDateRange() async {
@ -647,6 +708,10 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
? List.from(inProgressData)
: List.from(completedData);
if (SelectedStatus == 'Completed') {
selectedIndex = 1;
}
print('filteredData count: ${filteredData.length}');
print('inProgressData count: ${inProgressData.length}');
print('completedData count: ${completedData.length}');
@ -671,7 +736,180 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
}
}
String safeString(dynamic value) {
if (value == null) return '';
return value.toString();
}
void filterData(String query) {
_debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 300), () {
_applyFilter(query);
});
}
void _applyFilter(String query) {
print("FilterData - $query");
setState(() {
final sourceList = (selectedIndex == 0) ? inProgressData : completedData;
final q = query.trim().toLowerCase();
completedCurrentPage = 1;
if (q.length < 2) {
filteredData = List.from(sourceList);
return;
}
filteredData = sourceList.where((item) {
return safeString(item['agent_name']).toLowerCase().contains(q) ||
safeString(item['agent_code']).toLowerCase().contains(q) ||
safeString(item['reg_no']).toLowerCase().contains(q) ||
safeString(item['insured_name']).toLowerCase().contains(q) ||
safeString(item['policy_number']).toLowerCase().contains(q) ||
safeString(item['broker_name']).toLowerCase().contains(q) ||
safeString(item['insurer_name']).toLowerCase().contains(q) ||
safeString(item['insurer_short_name']).toLowerCase().contains(q) ||
safeString(item['enquiry_status']).toLowerCase().contains(q) ||
safeString(item['assigned_to_name']).toLowerCase().contains(q) ||
safeString(item['status']).toLowerCase().contains(q) ||
safeString(item['payment_mode']).toLowerCase().contains(q) ||
safeString(item['premium_amount']).toLowerCase().contains(q) ||
safeString(
_formatDate(item['created_on']),
).toLowerCase().contains(q) ||
safeString(
_formatDate(item['updated_on']),
).toLowerCase().contains(q);
}).toList();
print("FilterData2 - ${filteredData.length}");
});
}
// void _applyFilter(String query) {
// print("FilterData - $query");
// setState(() {
// // ALWAYS search from the FULL source list, not current filtered results
// final sourceList = (selectedIndex == 0) ? inProgressData : completedData;
//
// final q = query.trim().toLowerCase();
//
// if (q.length < 2) {
// filteredData = List.from(sourceList);
// return;
// }
//
// if (query.isEmpty) {
// // If search is empty, show all data for current tab
// filteredData = List.from(sourceList);
//
// print("FilterData1 - $filteredData");
// } else {
// // Search within ALL data from the current tab's source
// filteredData = sourceList.where((item) {
// final searchLower = query.toLowerCase();
//
// return safeString(
// item['agent_name'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// item['agent_code'],
// ).toLowerCase().contains(searchLower) ||
// safeString(item['reg_no']).toLowerCase().contains(searchLower) ||
// safeString(
// item['insurer_name'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// item['enquiry_status'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// item['assigned_to_name'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// item['insured_name'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// item['insurer_short_name'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// item['premium_amount']?.toString(),
// ).contains(searchLower) ||
// safeString(
// item['payment_mode'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// item['broker_name'],
// ).toLowerCase().contains(searchLower) ||
// safeString(
// _formatDate(item['created_on']),
// ).toLowerCase().contains(q) ||
// safeString(
// _formatDate(item['updated_on']),
// ).toLowerCase().contains(q) ||
// safeString(
// item['policy_number'],
// ).toLowerCase().contains(searchLower) ||
// safeString(item['status']).toLowerCase().contains(searchLower);
//
// // return safeString(
// // item['agent_name'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['agent_code'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['reg_no'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['insurer_name'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['enquiry_status'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['assigned_to_name'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['insured_name'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['insurer_short_name'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['premium_amount'] ?? '-',
// // ).toString().toLowerCase().contains(searchLower) ||
// // safeString(
// // item['payment_mode'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['broker_name'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // _formatDate(item['created_on']),
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // _formatDate(item['updated_on']),
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['policy_number'] ?? '-',
// // ).toLowerCase().contains(searchLower) ||
// // safeString(
// // item['status'] ?? '-',
// // ).toLowerCase().contains(searchLower);
// }).toList();
// print("FilterData2 - $filteredData");
// // Reset to page 1 when searching in completed tab
// if (selectedIndex == 1) {
// completedCurrentPage = 1;
// }
// }
// });
// }
void filterData1(String query) {
print("FilterData - $query");
setState(() {
// Get the correct source list based on selected tab
@ -730,6 +968,10 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
);
}).toList();
}
if (selectedIndex == 1) {
completedCurrentPage = 1;
}
});
}
@ -1537,6 +1779,17 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
),
SizedBox(width: 10),
MouseRegion(
onEnter: (_) {
setState(() {
isActionable = true;
});
},
onExit: (_) {
setState(() {
// isActionable = false;
isActionable = _searchStaffController.text.isNotEmpty;
});
},
child: ThemedSearchField(
hintText: 'Search',
backgroundColor: Color(0xFFFFFFFF),
@ -1614,49 +1867,13 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
'Assigned': 'Assigned',
'In progress': 'In Progress',
};
List<Map<String, dynamic>> get filteredDataByTab1 {
if (selectedIndex == 0) {
// Progress enquiries
final sortedData = [...inProgressData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
// final sortedData = [...filteredData]
// ..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
if (sortedData.isEmpty) return [];
return sortedData.where((e) {
return [
"To be assigned",
"Assigned",
"In progress",
].contains(e['enquiry_status']);
}).toList();
} else {
// final sortedData = [...filteredData]
// ..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
final sortedData = [...completedData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
if (sortedData.isEmpty) return [];
// Completed enquiries
return paginatedCompletedData;
// return sortedData.where((e) {
// return e['enquiry_status'] == "Completed";
// }).toList();
}
}
List<Map<String, dynamic>> get filteredDataByTab {
// Use filteredData instead of the raw source lists
final sortedData = [...filteredData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
if (sortedData.isEmpty) return [];
if (selectedIndex == 0) {
// Progress enquiries - return filtered in-progress items
return sortedData.where((e) {
return [
"To be assigned",
@ -1664,26 +1881,10 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
"In progress",
].contains(e['enquiry_status']);
}).toList();
} else {
// Completed enquiries - apply pagination to filtered completed items
final completedFiltered = sortedData.where((e) {
return e['enquiry_status'] == "Completed";
}).toList();
// Update total pages based on filtered results
totalCompletedPages = (completedFiltered.length / completedItemsPerPage)
.ceil();
if (totalCompletedPages == 0) totalCompletedPages = 1;
// Apply pagination
final startIndex = (completedCurrentPage - 1) * completedItemsPerPage;
final endIndex = (startIndex + completedItemsPerPage).clamp(
0,
completedFiltered.length,
);
return completedFiltered.sublist(startIndex, endIndex);
}
// Completed tab ALWAYS paginate
return paginatedCompletedData;
}
// Add group enquiries by status
@ -2647,7 +2848,12 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
: '-',
style: _dataBold,
),
Text(_formatDate(item['updated_on']), style: _dataBoldthm4),
Text(
item['updated_on'] != null
? _formatDate(item['updated_on'])
: '-',
style: _dataBoldthm4,
),
],
),
),
@ -2828,10 +3034,15 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
setState(() {
print('TAPPED TAB');
selectedIndex = index;
filteredData = (index == 0) ? inProgressData : completedData;
if (index == 1) {
completedCurrentPage = 1;
// final newValue = false;
// isActionable = newValue;
}
_searchStaffController.clear();
});
},
child: AnimatedContainer(
@ -3079,6 +3290,129 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
if (selectedIndex != 1 || completedData.length <= 25) {
return SizedBox.shrink();
}
// if (selectedIndex != 1 || completedData.length <= 25) {
// return SizedBox.shrink();
// }
// Calculate display values based on filteredData
final totalItems = filteredData.length;
final startItem = ((completedCurrentPage - 1) * completedItemsPerPage) + 1;
final endItem = (completedCurrentPage * completedItemsPerPage).clamp(
0,
totalItems,
);
return Container(
padding: EdgeInsets.symmetric(vertical: 2, horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Color(0xFFE2E8F0))),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
'Showing $startItem-$endItem of $totalItems', // Changed this
style: GoogleFonts.inter(
fontSize: 10,
color: Color(0xFF64748B),
),
),
SizedBox(width: 16),
// Items per page dropdown
Container(
padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFE2E8F0)),
borderRadius: BorderRadius.circular(6),
),
child: DropdownButton<int>(
value: completedItemsPerPage,
underline: SizedBox(),
isDense: true,
items: itemsPerPageOptions.map((int value) {
return DropdownMenuItem<int>(
value: value,
child: Text(
'$value / page',
style: GoogleFonts.inter(
fontSize: 10,
color: Color(0xFF334155),
),
),
);
}).toList(),
onChanged: (int? newValue) {
if (newValue != null) {
setState(() {
completedItemsPerPage = newValue;
completedCurrentPage = 1;
});
}
},
),
),
],
),
// Pagination buttons remain the same
Row(
children: [
IconButton(
icon: Icon(Icons.first_page, size: 18),
onPressed: completedCurrentPage > 1
? () => setState(() => completedCurrentPage = 1)
: null,
color: completedCurrentPage > 1
? Color(0xFF2E7D6E)
: Colors.grey,
tooltip: 'First page',
),
IconButton(
icon: Icon(Icons.chevron_left, size: 18),
onPressed: completedCurrentPage > 1
? () => setState(() => completedCurrentPage--)
: null,
color: completedCurrentPage > 1
? Color(0xFF2E7D6E)
: Colors.grey,
tooltip: 'Previous page',
),
..._buildPageNumbers(),
IconButton(
icon: Icon(Icons.chevron_right, size: 18),
onPressed: completedCurrentPage < totalCompletedPages
? () => setState(() => completedCurrentPage++)
: null,
color: completedCurrentPage < totalCompletedPages
? Color(0xFF2E7D6E)
: Colors.grey,
tooltip: 'Next page',
),
IconButton(
icon: Icon(Icons.last_page, size: 18),
onPressed: completedCurrentPage < totalCompletedPages
? () => setState(
() => completedCurrentPage = totalCompletedPages,
)
: null,
color: completedCurrentPage < totalCompletedPages
? Color(0xFF2E7D6E)
: Colors.grey,
tooltip: 'Last page',
),
],
),
],
),
);
}
Widget _buildPaginationControls1() {
if (selectedIndex != 1 || completedData.length <= 25) {
return SizedBox.shrink();
}
return Container(
padding: EdgeInsets.symmetric(vertical: 2, horizontal: 16),
@ -3093,7 +3427,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Row(
children: [
Text(
'Showing ${((completedCurrentPage - 1) * completedItemsPerPage) + 1}-${(completedCurrentPage * completedItemsPerPage).clamp(0, completedData.length)} of ${completedData.length}',
'Showing ${((completedCurrentPage - 1) * completedItemsPerPage) + 1}-${(completedCurrentPage * completedItemsPerPage).clamp(0, filteredData.length)} of ${filteredData.length}',
style: GoogleFonts.inter(
fontSize: 10,
color: Color(0xFF64748B),

View File

@ -423,6 +423,7 @@ class RaiseEnqFormState extends ConsumerState<RaiseEnqForm> {
buildVehicleNumber(context),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
buildRefresh(context),
SizedBox(width: 5),

View File

@ -775,7 +775,7 @@ class policylistState extends ConsumerState<policylist> {
context.push(AppRoutes.policyValidation, extra: item);
},
child: Container(
width: 110, // FIXED WIDTH Equal in both states
width: 80, // FIXED WIDTH Equal in both states
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
@ -787,8 +787,9 @@ class policylistState extends ConsumerState<policylist> {
borderRadius: BorderRadius.circular(5),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.center, // Center content
MainAxisAlignment.spaceEvenly, // Center content
children: [
Icon(
item['is_data_accuracy_checked'] == '1'
@ -797,12 +798,12 @@ class policylistState extends ConsumerState<policylist> {
size: 12,
color: Colors.white,
),
const SizedBox(width: 4),
const SizedBox(width: 1),
Text(
item['is_data_accuracy_checked'] == '1'
? 'Verified'
: 'Click to Verify',
style: const TextStyle(
: 'To Verify',
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w500,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,18 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import '../../../core/services/api_service.dart';
import '../../../data/models/bar_data.dart';
import '../../providers/manager_provider.dart';
import 'appChartColors.dart';
class CustomBarChart extends StatefulWidget {
class CustomBarChart extends ConsumerStatefulWidget {
final List<BarData> dataList;
final List<String> labels;
final List<String>? shortName;
final String title;
final String? btnName;
final double maxY;
@ -17,6 +22,7 @@ class CustomBarChart extends StatefulWidget {
super.key,
required this.dataList,
required this.labels,
this.shortName,
this.btnName,
this.title = "Horizontal Bar Chart",
this.maxY = 20,
@ -26,23 +32,27 @@ class CustomBarChart extends StatefulWidget {
final shadowColor = const Color(0xFFCCCCCC);
@override
State<CustomBarChart> createState() => _CustomBarChartState();
// State<CustomBarChart> createState() => _CustomBarChartState();
ConsumerState<CustomBarChart> createState() => _CustomBarChartState();
}
class _CustomBarChartState extends State<CustomBarChart> {
class _CustomBarChartState extends ConsumerState<CustomBarChart> {
int touchedGroupIndex = -1;
late int rotationTurns;
dynamic managerId;
late ApiService apiService;
@override
void initState() {
super.initState();
apiService = ApiService();
rotationTurns = widget.initialRotation;
// Future.microtask(() {
// managerId = ref.read(managerIdProvider);
// });
Future.microtask(() {
managerId = ref.read(managerIdProvider);
});
}
final indianFormatter = NumberFormat('#,##,##0', 'en_IN');
BarChartGroupData generateBarGroup(
int x,
Color color,
@ -53,180 +63,267 @@ class _CustomBarChartState extends State<CustomBarChart> {
return BarChartGroupData(
x: x,
groupVertically: false,
// showingTooltipIndicators: [0],
// barsSpace: 30,
barRods: [
BarChartRodData(toY: value1, color: Colors.cyan.shade300, width: 4),
BarChartRodData(toY: 0, color: Colors.transparent, width: 10),
BarChartRodData(
toY: value1,
color: Colors.cyan.shade300,
width: 20,
borderRadius: BorderRadius.circular(0),
),
// BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6),
BarChartRodData(toY: value2, color: Colors.orange.shade300, width: 4),
BarChartRodData(
toY: value2,
color: Colors.orange.shade300,
width: 20,
borderRadius: BorderRadius.circular(0),
),
],
showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
);
}
@override
// Widget build(BuildContext context) {
// return AspectRatio(
// aspectRatio: 1.4,
//
// );
// }
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1.4,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceBetween,
// verticalAxis: Axis.horizontal,
rotationQuarterTurns: rotationTurns,
return LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: BarChart(
BarChartData(
// alignment: BarChartAlignment.spaceBetween,
alignment: BarChartAlignment.start,
// verticalAxis: Axis.horizontal,
// rotationQuarterTurns: rotationTurns,
barTouchData: BarTouchData(
enabled: true,
handleBuiltInTouches: true,
touchTooltipData: BarTouchTooltipData(
tooltipMargin: 6,
tooltipRoundedRadius: 6,
tooltipPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
// ask fl_chart to fit tooltip inside available space
fitInsideVertically:
true, // if your fl_chart version supports it
fitInsideHorizontally:
true, // if your fl_chart version supports it
getTooltipItem:
(
BarChartGroupData group,
int groupIndex,
BarChartRodData rod,
int rodIndex,
) {
final textColor = rodIndex == 1
? Colors.cyan.shade300
: Colors.orange.shade300;
final textStyle = TextStyle(
color: textColor,
fontSize: 12,
fontWeight: FontWeight.w600,
);
barTouchData: BarTouchData(
enabled: true,
handleBuiltInTouches: true,
touchTooltipData: BarTouchTooltipData(
tooltipMargin: 6,
tooltipRoundedRadius: 6,
tooltipPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
// ask fl_chart to fit tooltip inside available space
fitInsideVertically: true, // if your fl_chart version supports it
fitInsideHorizontally:
true, // if your fl_chart version supports it
getTooltipItem:
(
BarChartGroupData group,
int groupIndex,
BarChartRodData rod,
int rodIndex,
) {
final textColor = rodIndex == 0
? Colors.cyan.shade300
: Colors.orange.shade300;
final textStyle = TextStyle(
color: textColor,
fontSize: 12,
fontWeight: FontWeight.w600,
final formattedValue = indianFormatter.format(
rod.toY.round(),
);
return BarTooltipItem(formattedValue, textStyle);
// return BarTooltipItem('${rod.toY.round()}', textStyle);
},
),
touchCallback: (FlTouchEvent e, BarTouchResponse? r) async {
// optional: change state to highlight tapped group
if (r == null || r.spot == null) {
setState(() => touchedGroupIndex = -1);
return;
}
setState(
() => touchedGroupIndex = r.spot!.touchedBarGroupIndex,
);
final spot = r.spot!;
final groupIndex = spot.touchedBarGroupIndex;
final rodIndex = spot.touchedRodDataIndex;
final barData = widget.dataList[groupIndex];
final name = barData.chartName;
final val = barData.id;
final month = rodIndex == 1 ? 'current' : 'previous';
print('name - $name');
print('val - $val');
print('month - $month');
print('managerId - $managerId');
if (e is FlTapUpEvent) {
debugPrint(
'Tapped value → ${rodIndex == 1 ? barData.value1 : barData.value2} -> ${barData.id}-'
' ${rodIndex == 1 ? 'current' : 'previous'} -> ${barData.chartName}',
);
return BarTooltipItem('${rod.toY.round()}', textStyle);
},
),
touchCallback: (FlTouchEvent e, BarTouchResponse? r) {
// optional: change state to highlight tapped group
if (r == null || r.spot == null) {
setState(() => touchedGroupIndex = -1);
return;
}
setState(() => touchedGroupIndex = r.spot!.touchedBarGroupIndex);
final spot = r.spot!;
final groupIndex = spot.touchedBarGroupIndex;
final rodIndex = spot.touchedRodDataIndex;
final barData = widget.dataList[groupIndex];
if (e is FlTapUpEvent) {
debugPrint(
'Tapped value → ${rodIndex == 0 ? barData.value1 : barData.value2} -> ${barData.id}-'
' ${rodIndex == 0 ? 'current' : 'previous'} -> ${barData.chartName}',
);
// await apiService.generateExcel()
}
},
),
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
right: BorderSide(color: AppColors.contentColorBlack, width: 0.1),
),
),
gridData: FlGridData(
show: true,
// drawVerticalLine: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) => FlLine(
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
color: Colors.blueGrey.shade100,
// color: Colors.black,
// color: AppColors.borderColor,
strokeWidth: 0.3,
),
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
axisNameWidget: widget.btnName != null
? Text(widget.btnName!)
: Text(''),
axisNameSize: 10,
sideTitles: SideTitles(
reservedSize: 60,
showTitles: true,
getTitlesWidget: (value, meta) {
final i = value.toInt();
if (i >= widget.labels.length) return const SizedBox();
return SideTitleWidget(
meta: meta,
child: Text(
widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 9.5,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.end,
),
);
if (managerId != null) {
print('managerId - $managerId');
await apiService.generateChartExcel(
name,
val,
month,
managerId,
);
}
}
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Transform.rotate(
angle: -3,
child: Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Text(
value.toInt().toString(),
textAlign: TextAlign.end,
style: GoogleFonts.inter(
fontSize: 9,
fontWeight: FontWeight.w400,
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
left: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
),
),
gridData: FlGridData(
show: true,
// drawVerticalLine: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) => FlLine(
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
color: Colors.blueGrey.shade100,
// color: Colors.black,
// color: AppColors.borderColor,
strokeWidth: 0.3,
),
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
axisNameWidget: widget.btnName != null
? Text(widget.btnName!)
: Text(''),
axisNameSize: 10,
sideTitles: SideTitles(
reservedSize: 30,
showTitles: true,
getTitlesWidget: (value, meta) {
final i = value.toInt();
if (i >= widget.labels.length) return const SizedBox();
final hasShortName =
widget.shortName != null &&
widget.shortName!.length > i &&
widget.shortName![i].trim().isNotEmpty;
return SideTitleWidget(
meta: meta,
child: hasShortName
? Tooltip(
message: widget.shortName![i],
child: Text(
widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 9.5,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.end,
),
)
: Text(
widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 9.5,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.end,
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
// interval: 10,
getTitlesWidget: (value, meta) {
// print("valuevalue - $value");
final text = value.toInt().toString();
final bool isLast = value == meta.max;
// if (isLast) {
// return const SizedBox.shrink(); // 👈 hide last label
// }
// print('value: $value');
// print('text: $text');
// print('length: ${text.length}');
// print('isLast: $isLast');
return Transform.rotate(
// angle: -3,
angle: 0,
child: Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Text(
isLast ? '0' : value.toInt().toString(),
textAlign: TextAlign.end,
style: GoogleFonts.inter(
fontSize: 9,
color: isLast ? Colors.white : Colors.black,
fontWeight: isLast
? FontWeight.w100
: FontWeight.w400,
),
),
),
),
),
);
},
reservedSize: 45,
);
},
reservedSize: 45,
),
),
rightTitles: const AxisTitles(),
topTitles: const AxisTitles(),
),
),
leftTitles: const AxisTitles(),
topTitles: const AxisTitles(),
),
barGroups: widget.dataList.asMap().entries.map((e) {
final index = e.key;
final data = e.value;
print('generateBarGroupBAR - $data');
return generateBarGroup(
index,
data.color,
data.value1,
data.value2,
data.shadowValue,
);
}).toList(),
barGroups: widget.dataList.asMap().entries.map((e) {
final index = e.key;
final data = e.value;
print('generateBarGroupBAR - $data');
return generateBarGroup(
index,
data.color,
data.value1,
data.value2,
data.shadowValue,
);
}).toList(),
// maxY: widget.maxY,
maxY: (widget.dataList != null && widget.dataList!.isNotEmpty)
? widget.dataList!
.map((e) => e.value1 > e.value2 ? e.value1 : e.value2)
.reduce((a, b) => a > b ? a : b) +
15
: 20,
),
),
// maxY: widget.maxY,
maxY: (widget.dataList != null && widget.dataList!.isNotEmpty)
? widget.dataList!
.map(
(e) => e.value1 > e.value2 ? e.value1 : e.value2,
)
.reduce((a, b) => a > b ? a : b) +
15
: 20,
),
),
);
},
);
}
}

View File

@ -1,8 +1,10 @@
// custom_stacked_bar_chart.dart
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../core/services/api_service.dart';
import '../../../data/models/bar_data.dart';
import 'package:fl_chart/fl_chart.dart';
@ -13,10 +15,12 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../providers/manager_provider.dart';
import 'appChartColors.dart';
class CustomStackedBarChart extends StatefulWidget {
class CustomStackedBarChart extends ConsumerStatefulWidget {
final List<String> labels;
final List<String> labelsId;
final List<dynamic> dataList;
final double maxY;
final int initialRotation;
@ -27,6 +31,7 @@ class CustomStackedBarChart extends StatefulWidget {
const CustomStackedBarChart({
super.key,
required this.labels,
required this.labelsId,
required this.dataList,
this.maxY = 100.0,
this.initialRotation = 1,
@ -36,16 +41,23 @@ class CustomStackedBarChart extends StatefulWidget {
});
@override
State<CustomStackedBarChart> createState() => _CustomStackedBarChartState();
ConsumerState<CustomStackedBarChart> createState() =>
_CustomStackedBarChartState();
}
class _CustomStackedBarChartState extends State<CustomStackedBarChart> {
class _CustomStackedBarChartState extends ConsumerState<CustomStackedBarChart> {
late int rotationTurns;
dynamic managerId;
late ApiService apiService;
@override
void initState() {
super.initState();
apiService = ApiService();
rotationTurns = widget.initialRotation;
Future.microtask(() {
managerId = ref.read(managerIdProvider);
});
}
@override
@ -191,6 +203,37 @@ class _CustomStackedBarChartState extends State<CustomStackedBarChart> {
);
},
),
touchCallback: (event, response) async {
if (response == null || response.spot == null) return;
// ONLY handle clicks
if (event is! FlTapUpEvent) return;
final spot = response.spot!;
final int groupIndex = spot.touchedBarGroupIndex;
final int rodIndex = spot.touchedRodDataIndex;
final int stackIndex = spot.touchedStackItemIndex!;
final label =
widget.vehicleTypeNames?[groupIndex][stackIndex] ?? 'Unknown';
final isCurrentMonth = rodIndex == 0;
final monthLabel = isCurrentMonth ? 'Current' : 'Previous';
final categoryLabel = widget.labels[groupIndex];
final categoryLabelID = widget.labelsId[groupIndex];
debugPrint(
'Clicked stack → $label -> $monthLabel ->$managerId ->$categoryLabel->$categoryLabelID',
);
await apiService.generatePerformanceDashboardExcel(
'StaffProduct',
label,
categoryLabelID,
monthLabel,
managerId,
);
},
),
barGroups: widget.productsList.asMap().entries.map((entry) {

View File

@ -34,6 +34,9 @@ class ThemedFormField extends HookWidget {
this.horizonalPad,
this.enableBorderWidth,
this.textColr,
this.errFieldFont,
this.errFieldHgt,
});
final double? verticalPad;
@ -53,6 +56,8 @@ class ThemedFormField extends HookWidget {
final Color? borderColor;
final Color? errorBorderColor;
final Color? errorTextColor;
final double? errFieldFont;
final double? errFieldHgt;
final double? txtwidth;
final bool readOnly;
final bool isdense;
@ -91,7 +96,13 @@ class ThemedFormField extends HookWidget {
);
final inputDecoration = InputDecoration(
isDense: isdense,
errorStyle: TextStyle(color: Color(0xFFD83731)),
// errorStyle: TextStyle(color: Color(0xFFD83731)),
// errorStyle: TextStyle(
// color: Color(0xFFD83731),
// fontSize: errFieldFont ?? 12,
// height: errFieldHgt ?? 10,
// ),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(
@ -140,7 +151,7 @@ class ThemedFormField extends HookWidget {
borderSide: BorderSide(color: borderColor ?? Color(0xFFFFFFFF)),
)
: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderRadius: BorderRadius.circular(bordedRad ?? 10),
borderSide: BorderSide(
color: highlightColor ?? borderColor ?? Color(0xFF50A398),
width: 2,

View File

@ -182,6 +182,7 @@ class ThemedFormInlineField extends HookWidget {
suffixIcon: isObscurable ? obscureBtn : null,
);
return Container(
width: txtwidth ?? MediaQuery.of(context).size.width,
height: txtheight ?? null,
child: DecoratedBox(
decoration: boxDecoration,

View File

@ -11,6 +11,8 @@ import '../../core/routing/routes.dart';
import '../layouts/responsive_layout.dart';
import '../providers/manager_provider.dart';
import '../providers/userRoleProvider.dart';
import '../screens/staff/Enquiry/Proposal_QuickCreation/Proposal_QuickCreation.dart';
import '../themes/indicators/side_Drawer_Panel.dart';
class TopBar extends ConsumerStatefulWidget implements PreferredSizeWidget {
final String title;
@ -141,6 +143,32 @@ class _TopBarState extends ConsumerState<TopBar> {
),
],
SizedBox(width: 10),
if (role != 'Accounts') ...[
// Proposal button
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade200, width: 1),
),
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.quora, color: Colors.black87, size: 20),
onPressed: () {
SideDrawerPanel.show(
context: context,
title: 'Quote Creation',
child: CreateProposal_Quick(),
);
},
tooltip: 'Quick Quote',
),
),
const SizedBox(width: 12),
],
Container(
width: 40,
height: 40,

View File

@ -700,26 +700,26 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.10"
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.1"
lints:
dependency: transitive
description:
@ -1297,10 +1297,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.6"
version: "0.7.4"
toastification:
dependency: "direct main"
description:
@ -1417,10 +1417,10 @@ packages:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.1.4"
vm_service:
dependency: transitive
description: