Enquiry_Staff_Module

This commit is contained in:
venbaittech 2025-09-29 09:31:15 +05:30
parent cf5107f764
commit 4acdbca2b2
38 changed files with 5908 additions and 1622 deletions

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -18,6 +18,7 @@ import '../../presentation/screens/UserManagement/Staff/staffList.dart';
import '../../presentation/screens/home/home_screen.dart';
import '../../presentation/screens/login/login_screen.dart';
import '../../presentation/screens/splash/splash_screen.dart';
import '../../presentation/screens/staff/Enquiry/enquiry_list.dart';
import '../../presentation/screens/staff/policy/policy.dart';
import '../../presentation/screens/staff/policy/policy_list.dart';
import '../../presentation/screens/staff/quotations/quotation.dart';
@ -145,6 +146,15 @@ final GoRouter appRouter = GoRouter(
path: AppRoutes.policy,
builder: (context, state) => const PolicyScreen(),
),
GoRoute(
path: AppRoutes.policy,
builder: (context, state) => const PolicyScreen(),
),
GoRoute(
path: AppRoutes.enquiryForStaff,
builder: (context, state) => const EnquiryStaff(),
),
],
redirect: (context, state) async {
final loggedIn = await AuthService.isLoggedIn();

View File

@ -9,6 +9,7 @@ class AppRoutes {
static const String staffLst = '/staffLst';
static const staff = '/staff/:id';
static const String enquiryLst = '/enquiryLst';
static const String enquiryForStaff = '/enquiryForStaff';
static const String tabEnquiry = '/tabEnquiry';
static const String claimlist = '/claimList';

View File

@ -156,7 +156,14 @@ class ApiService {
Future<void> getPdfDownload(String path, String id) async {
print('getPdfDownload 1');
print('getPdfDownload 1 - $path');
if (path.isEmpty) {
ToastHelper.showInfoToast(context, 'No file path found.');
return;
}
final url = Uri.parse('https://venbait.in/nhance/partner/dev/$path');
await _initializeToken();
print('getPdfDownload 2');
if (_token == null) throw Exception('Token not found. Please log in.');
@ -205,7 +212,10 @@ class ApiService {
],
),
);
} else if (response.statusCode == 500) {
ToastHelper.showInfoToast(context, 'No File Not Found');
} else {
ToastHelper.showInfoToast(context, 'No File Not Found');
throw Exception('Failed to download file');
}
}
@ -279,6 +289,34 @@ class ApiService {
return response;
}
// ----------------------------------- Dashboard -------------------------------------------------
Future<Map<String, dynamic>> fetchDashboard(int id, role) async {
// print(_token);
if (_token == null) {
await _initializeToken();
}
final url;
if (role == 'manager') {
url = Uri.parse('${Env.apiUrl}dashboard/managerDashboard?manager_id=$id');
} else if (role == 'staff') {
url = Uri.parse('${Env.apiUrl}dashboard/staffDashboard?staff_id=$id');
} else {
url = Uri.parse('${Env.apiUrl}dashboard/agentDashboard?agent_id=$id');
}
// final url = Uri.parse(
// 'https://venbait.in/nhance/partner/dev/api/agent/agentList?manager_id=${managerId}',
// );
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'App-Signature': 'nhance-partner-2025-signature-35468846JRhH551HK',
};
final response = await _makeGetRequest(url, headers);
return response;
}
// -------------------------------- AGENT ----------------------------------------------
Future<Map<String, dynamic>> fetchAgentIncentiveList(agentId) async {
@ -544,6 +582,38 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> fetchPolicyDataOnlyList(int id, role) async {
// print(_token);
if (_token == null) {
await _initializeToken();
}
final userId = '1';
final url;
if (role == 'manager') {
url = Uri.parse(
'${Env.apiUrl}enquiry/enquiryList?manager_id=$id&only_policy_data=true',
);
} else {
url = Uri.parse(
'${Env.apiUrl}enquiry/enquiryList?staff_id=$id&only_policy_data=true',
);
}
// else {
// url = Uri.parse('${Env.apiUrl}endorsement/endorsementList?agent_id=$id');
// }
// final url = Uri.parse(
// 'https://venbait.in/nhance/partner/dev/api/agent/agentList?manager_id=${managerId}',
// );
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'App-Signature': 'nhance-partner-2025-signature-35468846JRhH551HK',
};
final response = await _makeGetRequest(url, headers);
return response;
}
// ----------------------------------- CLAIMS -------------------------------------------------
Future<Map<String, dynamic>> fetchClaimList(int id, role) async {

View File

@ -76,6 +76,17 @@ class Validators {
return null;
}
static String? doubleNumber(String? value, String label) {
if (value == null || value.trim().isEmpty) {
return "$label is required";
}
// allow integers or decimals with optional fraction part
if (!RegExp(r'^[0-9]+(\.[0-9]+)?$').hasMatch(value.trim())) {
return "$label must be a valid number";
}
return null;
}
static String? phone(String? value, String label) {
if (value == null || value.trim().isEmpty) {
return "Required";

View File

@ -36,18 +36,18 @@ class MainLayout extends StatelessWidget {
),
drawer: const DrawerMenu(),
body: body,
bottomNavigationBar: MobileTabs(
onTabChanged: (index) {
debugPrint("Mobile tab changed to $index");
if (index == 0) {
context.go(AppRoutes.dashboard);
} else if (index == 1) {
context.go(AppRoutes.profile);
} else if (index == 2) {
// ref.read(enquiryIdProvider.notifier).state = null;
context.go(AppRoutes.enquiryLst);
}
},
bottomNavigationBar: MobileBottomMenu(
// onTabChanged: (index) {
// debugPrint("Mobile tab changed to $index");
// if (index == 0) {
// context.go(AppRoutes.dashboard);
// } else if (index == 1) {
// context.go(AppRoutes.profile);
// } else if (index == 2) {
// // ref.read(enquiryIdProvider.notifier).state = null;
// context.go(AppRoutes.enquiryLst);
// }
// },
),
),

View File

@ -0,0 +1,3 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
final quotationStaffIdProvider = StateProvider<String?>((ref) => null);

View File

@ -140,6 +140,15 @@ class EnquiryTabState extends ConsumerState<EnquiryTab> {
print("APISERTOKEN - $_token");
}
@override
void dispose() {
// Dispose all TextEditingControllers
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
Future<void> getVehicleType() async {
print('getClaimList called');
setState(() {

View File

@ -102,6 +102,15 @@ class _AddDialogState extends State<AddDialog> {
print("APISERTOKEN - $_token");
}
@override
void dispose() {
// Dispose all TextEditingControllers
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
Future<void> getClaimsType() async {
print('getClaimList called');
setState(() {
@ -244,12 +253,6 @@ class _AddDialogState extends State<AddDialog> {
}
}
@override
void dispose() {
// controllers.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(

View File

@ -81,6 +81,15 @@ class PolicyTabState extends ConsumerState<PolicyTab> {
});
}
@override
void dispose() {
// Dispose all TextEditingControllers
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");

View File

@ -123,6 +123,15 @@ class QuotationTabState extends ConsumerState<QuotationTab> {
getQuotationList(managerId);
}
@override
void dispose() {
// Dispose all TextEditingControllers
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
Future<void> getQuotationList(managerId) async {
print('getClaimList called MagId - $managerId');
setState(() {

View File

@ -106,7 +106,7 @@ class EnquiryListState extends ConsumerState<EnquiryList> {
filteredData = getStaffData.where((item) {
// final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
return (item['created_on'] ?? '').toLowerCase().contains(
return (_formatDate(item['created_on']) ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['reg_no'] ?? '').toLowerCase().contains(
@ -142,10 +142,12 @@ class EnquiryListState extends ConsumerState<EnquiryList> {
print('EDITStaff - ${data['id']}');
// dynamic id = data['id'];
// context.go('/tabEnquiry/$id');
setState(() {
final id = data['id'].toString();
ref.read(enquiryIdProvider.notifier).state = id;
});
// update provider
final id = data['id'].toString();
ref.read(enquiryIdProvider.notifier).state = id;
context.go(AppRoutes.tabEnquiry);
},
child: Row(

View File

@ -31,6 +31,8 @@ class claimListState extends ConsumerState<claimList> {
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
final TextEditingController _searchStaffController = TextEditingController();
@override
void initState() {
super.initState();
@ -114,25 +116,23 @@ class claimListState extends ConsumerState<claimList> {
(item['policy_no'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['policy_end_date'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['claim_type'] ?? '').toLowerCase().contains(
(item['claim_type_value'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['claim_number'] ?? '').toLowerCase().contains(
query.toLowerCase(),
);
) ||
(_formatDate(
item['policy_end_date'],
).toLowerCase()).contains(query.toLowerCase());
}).toList();
});
}
final TextEditingController _searchStaffController = TextEditingController();
String _formatDate(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat('dd/MM/yyyy HH:mm').format(dateTime); // 24-hour format
return DateFormat('dd/MM/yyyy').format(dateTime); // date only
} catch (e) {
return rawDate; // fallback if parsing fails
}
@ -323,17 +323,17 @@ class claimListState extends ConsumerState<claimList> {
),
),
Expanded(
flex: 2,
flex: 3,
child: Text('Claim Type', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Claim No.', style: _headerStyle),
),
Expanded(
flex: 1,
child: Text('Action', style: _headerStyle),
),
// Expanded(
// flex: 1,
// child: Text('Action', style: _headerStyle),
// ),
],
),
),
@ -425,86 +425,47 @@ class claimListState extends ConsumerState<claimList> {
Expanded(
flex: 2,
child: Text(
item['reg_no'] != null
? _formatDate(item['reg_no'])
: '-', // fallback
item['reg_no'] ?? '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
item['insurer_name'] != null
? _formatDate(item['insurer_name'])
: '-', // fallback
item['insurer_name'] ?? '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(item['policy_no'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(
// _formatDate(item['policy_end_date']) ?? '-',
item['policy_end_date'] != null
? _formatDate(item['policy_end_date'])
: '-',
style: _dataBold,
),
),
Expanded(
flex: 3,
child: Text(
item['claim_type_value'] ?? '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['policy_no']) ?? '-',
item['claim_number'] ?? '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['policy_end_date']) ?? '-',
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
item['claim_type'] != null
? _formatDate(item['claim_number'])
: '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
item['claim_number'] != null
? _formatDate(item['claim_number'])
: '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 1,
child: Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(
Icons.more_vert,
color: Color(0xFF475569),
size: 14,
),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
),
),
],
),
],
),
),
// Expanded(s
// Expanded(
// flex: 1,
// child: Row(
// children: [
@ -561,24 +522,24 @@ class claimListState extends ConsumerState<claimList> {
item['reg_no'] != null ? _formatDate(item['reg_no']) : '-',
style: _headerStyle,
),
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
),
),
],
),
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14),
// itemBuilder: (context) => [
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.center,
// children: _buildPopupMenuActions(context, item),
// ),
// ),
// ),
// ],
// ),
],
),
const Divider(color: Color(0xffD9EBE8), thickness: 0.8),
@ -597,8 +558,8 @@ class claimListState extends ConsumerState<claimList> {
//company
const Text("Company", style: _cardheaderStyle),
Text(
item['reg_no'] != null
? _formatDate(item['reg_no'])
item['policy_end_date'] != null
? _formatDate(item['policy_end_date'])
: '-',
style: _cardBodyStyle,
maxLines: 3,
@ -616,7 +577,7 @@ class claimListState extends ConsumerState<claimList> {
//status
const Text("Claim Type", style: _cardheaderStyle),
Text(
item['claim_type'] ?? '-',
item['claim_type_value'] ?? '-',
style: _cardBodyStyle,
maxLines: 2,
softWrap: true,
@ -638,10 +599,7 @@ class claimListState extends ConsumerState<claimList> {
children: [
//date and time
const Text("policy Number", style: _cardheaderStyle),
Text(
_formatDate(item['policy_no']) ?? '-',
style: _cardBodyStyle,
),
Text(item['policy_no'] ?? '-', style: _cardBodyStyle),
],
),
),

View File

@ -184,7 +184,7 @@ class endosementState extends ConsumerState<endosement> {
color: Color(0xFF425B5B),
),
Text(
"Endosement",
"Endorsement",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
@ -325,10 +325,10 @@ class endosementState extends ConsumerState<endosement> {
style: _headerStyle,
),
),
Expanded(
flex: 1,
child: Text('Action', style: _headerStyle),
),
// Expanded(
// flex: 1,
// child: Text('Action', style: _headerStyle),
// ),
],
),
),
@ -420,74 +420,65 @@ class endosementState extends ConsumerState<endosement> {
Expanded(
flex: 2,
child: Text(
item['reg_no'] != null
? _formatDate(item['reg_no'])
: '-', // fallback
item['reg_no'] ?? '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
item['insurer_name'] != null
? _formatDate(item['insurer_name'])
: '-', // fallback
item['insurer_name'] ?? '-', // fallback
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(item['policy_number'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['policy_number']) ?? '-',
item['endorsement_type_value'] ?? '-',
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['endorsement_no']) ?? '-',
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['endorsement_type']) ?? '-',
style: _dataBold,
),
),
Expanded(
flex: 1,
child: Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(
Icons.more_vert,
color: Color(0xFF475569),
size: 14,
),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
),
),
],
),
],
),
child: Text(item['endorsement_no'] ?? '-', style: _dataBold),
),
// Expanded(
// flex: 1,
// child: Row(
// children: [
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(
// Icons.more_vert,
// color: Color(0xFF475569),
// size: 14,
// ),
// itemBuilder: (context) => [
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 8,
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.center,
// children: _buildPopupMenuActions(context, item),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ),
],
),
);
@ -509,28 +500,25 @@ class endosementState extends ConsumerState<endosement> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//reg no
Text(
item['reg_no'] != null ? _formatDate(item['reg_no']) : '-',
style: _headerStyle,
),
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
),
),
],
),
Text(item['reg_no'] ?? '-', style: _headerStyle),
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14),
// itemBuilder: (context) => [
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.center,
// children: _buildPopupMenuActions(context, item),
// ),
// ),
// ),
// ],
// ),
],
),
const Divider(color: Color(0xffD9EBE8), thickness: 0.8),
@ -549,9 +537,7 @@ class endosementState extends ConsumerState<endosement> {
//company
const Text("Company", style: _cardheaderStyle),
Text(
item['insurer_name'] != null
? _formatDate(item['insurer_name'])
: '-',
item['insurer_name'] ?? '-',
style: _cardBodyStyle,
maxLines: 3,
softWrap: true,
@ -568,7 +554,7 @@ class endosementState extends ConsumerState<endosement> {
//status
const Text("Endorsement Type", style: _cardheaderStyle),
Text(
item['endorsement_type'] ?? '-',
item['endorsement_type_value'] ?? '-',
style: _cardBodyStyle,
maxLines: 2,
softWrap: true,

View File

@ -15,6 +15,7 @@ import '../../../../core/config/env.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/Pagination.dart';
import '../../../../data/utils/toastNotification.dart';
import '../../../../data/utils/validators.dart';
import '../../../layouts/main_layout.dart';
import '../../../providers/manager_provider.dart';
@ -237,6 +238,15 @@ class AgentState extends ConsumerState<Agent> {
print("✅ Agent submitted successfully!");
print("Response: ${response.body}");
isUpdating
? ToastHelper.showSuccessToast(
context,
'Agent Updated Successfully',
)
: ToastHelper.showSuccessToast(
context,
'Agent Created Successfully',
);
context.go(AppRoutes.agentLst);
} else {
final responseBody = jsonDecode(response.body);

View File

@ -1,5 +1,6 @@
// import 'dart:io' as html;
// import 'dart:nativewrappers/_internal/vm/lib/typed_data_patch.dart';
import 'dart:convert';
import 'dart:typed_data'; // Import for Uint8List
import 'package:dropdown_search/dropdown_search.dart';
import 'package:file_picker/file_picker.dart';
@ -8,7 +9,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:nhance_partner/data/utils/toastNotification.dart';
import 'package:nhance_partner/presentation/themes/indicators/text_field_theme.dart';
import 'package:toastification/toastification.dart';
import 'package:universal_html/html.dart' as html;
import '../../../../core/config/env.dart';
@ -202,25 +205,31 @@ class UploadIncentiveModalState extends ConsumerState<UploadIncentiveModal> {
}
Future<void> handleSave() async {
if (!_formKey.currentState!.validate()) return;
print("Handl1");
final dataSet = dataDetails();
print("dataSetAgent - $dataSet");
print("Handl13f");
if (agentId == null) {
ToastHelper.showErrorToast(context, 'Select Agent Name');
} else {
if (!_formKey.currentState!.validate()) return;
print("Handl1");
final dataSet = dataDetails();
print("dataSetAgent - $dataSet");
print("Handl13f");
if (passportFile == null) {
print("❌ No file selected!");
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Please select a file before submitting")),
);
return;
if (passportFile == null) {
print("❌ No file selected!");
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please select a file before submitting"),
),
);
return;
}
setState(() {
uploadIncentiveData(dataSet);
});
print("Handl2");
}
setState(() {
uploadIncentiveData(dataSet);
});
print("Handl2");
}
Future<void> uploadIncentiveData(Map<String, dynamic> dataSet) async {
@ -303,6 +312,7 @@ class UploadIncentiveModalState extends ConsumerState<UploadIncentiveModal> {
final response = await http.Response.fromStream(streamedResponse);
print("Response status: ${response.statusCode}");
print("Response body: ${response.body}");
final responseBody = json.decode(response.body);
if (response.statusCode == 200 || response.statusCode == 201) {
// dispose();
@ -313,7 +323,19 @@ class UploadIncentiveModalState extends ConsumerState<UploadIncentiveModal> {
final data = dataDetails();
final agentId = data["agent_id"];
getAgentIncenctiveFileList(agentId);
final status = responseBody['status'];
final message = responseBody['data'] ?? '';
if (status == 'success') {
ToastHelper.showSuccessToast(context, 'File uploaded successfully!');
getAgentIncenctiveFileList(agentId);
} else {
ToastHelper.showErrorToast(
context,
message.isNotEmpty ? message : 'Something went wrong!',
);
}
// context.go(AppRoutes.agentLst);
} else {
print("❌ Submission failed. Status: ${response.statusCode}");

View File

@ -485,37 +485,68 @@ class AgentListState extends ConsumerState<AgentList> {
),
Expanded(
flex: 1,
child: Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(
Icons.more_vert,
color: Color(0xFF475569),
size: 14,
),
itemBuilder: (context) => [
if (item['is_active'] == "1")
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
child: item['is_active'] == "1"
? Row(
children: [
GestureDetector(
onTap: () {
// Navigator.pop(context);
print('EDITStaff - ${item['id']}');
dynamic id = item['id'];
context.go('/agent/$id');
},
child: Image.asset(
"assets/miscellaneous/Edit.png",
height: 15,
width: 15,
),
),
],
),
],
),
],
)
: Row(
children: [
Image.asset(
"assets/miscellaneous/Edit_muted.png",
height: 15,
width: 15,
),
],
),
),
// Expanded(
// flex: 1,
// child: Row(
// children: [
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(
// Icons.more_vert,
// color: Color(0xFF475569),
// size: 14,
// ),
// itemBuilder: (context) => [
// if (item['is_active'] == "1")
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 8,
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.center,
// children: _buildPopupMenuActions(context, item),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ),
],
),
);

View File

@ -155,8 +155,22 @@ class ProfileState extends ConsumerState<Profile> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (!isProfile) ...[
IconButton(
onPressed: () {
setState(() {
isProfile = true;
});
},
icon: Icon(
Icons.arrow_back_ios_new_outlined,
size: 16,
),
),
SizedBox(width: 4),
],
Text(
isProfile ? '' : 'Incentive Files',
style: _topheaderStyle,
@ -168,7 +182,7 @@ class ProfileState extends ConsumerState<Profile> {
? Container(
color: Colors.white,
padding: ResponsiveLayout.isMobile(context)
? EdgeInsets.all(16.0)
? EdgeInsets.all(8.0)
: null,
child: isLoading
? const Center(
@ -176,10 +190,10 @@ class ProfileState extends ConsumerState<Profile> {
)
: Column(
children: [
const SizedBox(height: 25),
// const SizedBox(height: 25),
Container(
width: 70,
height: 70,
width: 60,
height: 60,
decoration: BoxDecoration(
color: Color(0xFFD9D9D9),
borderRadius: BorderRadius.circular(
@ -188,7 +202,7 @@ class ProfileState extends ConsumerState<Profile> {
),
child: Icon(
Icons.person_outline,
size: 50,
size: 30,
color: Colors.black,
),
),
@ -669,7 +683,7 @@ class ProfileState extends ConsumerState<Profile> {
static const _topheaderStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 19,
fontSize: 14,
);
}

View File

@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
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 '../../../../core/config/env.dart';
@ -159,6 +160,16 @@ class StaffState extends ConsumerState<Staff> {
if (response.statusCode == 200) {
print("Staff submitted successfully!");
print("Response: ${response.body}");
isUpdating
? ToastHelper.showSuccessToast(
context,
'Staff Updated Successfully',
)
: ToastHelper.showSuccessToast(
context,
'Staff Created Successfully',
);
context.go(AppRoutes.staffLst);
} else {
final responseBody = jsonDecode(response.body);

View File

@ -434,37 +434,68 @@ class StaffListState extends ConsumerState<StaffList> {
Expanded(
flex: 1,
child: Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(
Icons.more_vert,
color: Color(0xFF475569),
size: 14,
),
itemBuilder: (context) => [
if (item['is_active'] == "1")
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
child: item['is_active'] == "1"
? Row(
children: [
GestureDetector(
onTap: () {
// Navigator.pop(context);
print('EDITStaff - ${item['id']}');
dynamic id = item['id'];
context.go('/staff/$id');
},
child: Image.asset(
"assets/miscellaneous/Edit.png",
height: 15,
width: 15,
),
),
],
),
],
),
],
)
: Row(
children: [
Image.asset(
"assets/miscellaneous/Edit_muted.png",
height: 15,
width: 15,
),
],
),
),
// Expanded(
// flex: 1,
// child: Row(
// children: [
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(
// Icons.more_vert,
// color: Color(0xFF475569),
// size: 14,
// ),
// itemBuilder: (context) => [
// if (item['is_active'] == "1")
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 8,
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.center,
// children: _buildPopupMenuActions(context, item),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ),
],
),
);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,914 @@
// import 'dart:io' as html;
import 'package:http/http.dart' as http;
import 'package:dropdown_search/dropdown_search.dart';
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:intl/intl.dart';
import '../../../../core/config/env.dart';
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import 'package:toastification/toastification.dart';
import 'package:universal_html/html.dart' as html;
import '../../../../core/routing/routes.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/Pagination.dart';
import '../../../../data/utils/toastNotification.dart';
import '../../../../data/utils/validators.dart';
import '../../../layouts/main_layout.dart';
import '../../../layouts/responsive_layout.dart';
import '../../../providers/manager_provider.dart';
import '../../../providers/quotation_staff_proivder.dart';
import '../../../providers/userRoleProvider.dart';
import '../../../themes/indicators/customizd_file_upload.dart';
import '../../../themes/indicators/date_field_theme.dart';
import '../../../themes/indicators/export_btn.dart';
import '../../../themes/indicators/search_field_theme.dart';
import '../../../themes/indicators/text_field_theme.dart';
import 'package:file_picker/file_picker.dart';
import '../../../widgets/custom_action_popup.dart';
import '../assignStaff.dart';
class EnquiryStaff extends ConsumerStatefulWidget {
const EnquiryStaff({super.key});
@override
ConsumerState<EnquiryStaff> createState() => EnquiryStaffState();
}
class EnquiryStaffState extends ConsumerState<EnquiryStaff> {
int currentPage = 1;
int itemsPerPage = 10;
late ApiService apiService;
// List<Map<String, dynamic>> dataVal = [];
List<Map<String, dynamic>> getStaffData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
dynamic roleId;
@override
void initState() {
super.initState();
apiService = ApiService();
Future.microtask(() {
final id = ref.read(managerIdProvider);
roleId = ref.read(userRoleProvider);
print("ENQroleId - $roleId");
if (id != null) {
getStaffList(id, roleId);
}
});
}
void refresh() {
final id = ref.read(managerIdProvider);
final roleId = ref.read(userRoleProvider);
if (id != null) {
getStaffList(id, roleId);
}
}
Future<void> getStaffList(int managerId, role) async {
print('getClaimList called MagId - $managerId - $role');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchPolicyList(managerId, role);
if (response['status'] == 'success') {
final data = response['data'];
print('getStaffListData - ${response['data']}');
setState(() {
if (data is List) {
// Already a list of maps
getStaffData = List<Map<String, dynamic>>.from(data);
} else if (data is Map) {
// Single object, wrap in a list
getStaffData = [Map<String, dynamic>.from(data)];
} else {
getStaffData = [];
}
// 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<dynamic> get _paginatedData {
// Sort descending by id first
final sortedData = [...filteredData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
// final sortedData = [...filteredData];
final startIndex = (currentPage - 1) * itemsPerPage;
final endIndex = (currentPage * 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['agent_name'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['reg_no'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['insurer_name'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['assigned_to_name'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['insured_name'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['premium_amount'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['payment_mode'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(_formatDate(item['updated_on']) ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['policy_number'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['status'] ?? '').toLowerCase().contains(query.toLowerCase());
}).toList();
});
}
final TextEditingController _searchStaffController = TextEditingController();
String _formatDate(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat('dd/MM/yyyy HH:mm').format(dateTime); // 24-hour format
} catch (e) {
return rawDate; // fallback if parsing fails
}
}
List<Widget> _buildPopupMenuActions(
BuildContext context,
dynamic data,
id,
regNum,
) {
return [
if (roleId == 'manager') ...[
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (ctx) => AssignStaffDialog(
enquiryPrimaryId: id,
regNum: regNum,
userId: 1,
onSubmit: (value) {
debugPrint("New assignY: $value");
refresh();
},
),
);
},
hoverColor: Color(0xFFE3F1F0),
splashColor: Color(0xFFE3F1F0),
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Row(
// mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
"assets/miscellaneous/image_3.png",
// height: 45,
// width: 15,
),
SizedBox(width: 10),
Text('Assign Staff'),
],
),
),
),
),
],
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
ref.read(quotationStaffIdProvider.notifier).state = id;
context.go(AppRoutes.quotation);
},
hoverColor: Color(0xFFE3F1F0),
splashColor: Color(0xFFE3F1F0),
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Row(
// mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
"assets/miscellaneous/image_1.png",
height: 15,
width: 15,
),
const SizedBox(width: 10),
((data['status'] == 'Policy Created') ||
(data['status'] == 'Quotation Accepted'))
? const Text('View Quotation')
: const Text('Create Quotation'),
],
),
),
),
),
if (data['status'] == 'Quotation Accepted' ||
data['status'] == 'Policy Created')
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
ref.read(quotationStaffIdProvider.notifier).state = id;
context.go(AppRoutes.policy);
},
hoverColor: Color(0xFFE3F1F0),
splashColor: Color(0xFFE3F1F0),
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Row(
// mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
"assets/miscellaneous/image_2.png",
height: 15,
width: 15,
),
const SizedBox(width: 10),
data['status'] == 'Policy Created'
? Text('View Policy')
: Text('Create Policy'),
],
),
),
),
),
];
}
@override
Widget build(BuildContext context) {
return MainLayout(
title: "Enquiries",
body: Container(
// 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: [
Icon(
Icons.arrow_left_sharp,
size: 35,
color: Color(0xFF425B5B),
),
Text(
"Enquiries",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
SizedBox(height: 10),
Expanded(
child: Container(
// color: Colors.green,
// color: Colors.green.shade50,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(8.0),
child: Column(
children: [
Container(
// height: 40,
// color: Colors.pink,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
ThemedSearchField(
hintText: 'Search',
backgroundColor: Color(0xFFF6F8F8),
onChanged: filterData,
controller: _searchStaffController,
txtwidth: ResponsiveLayout.isMobile(context)
? MediaQuery.of(context).size.width * 0.6
: MediaQuery.of(context).size.width * 0.2,
),
Spacer(),
ExportBtn(
sheetName: "Policy",
fileName: "Policy_list",
data: filteredData,
txt: !ResponsiveLayout.isMobile(context)
? true
: false,
headers: [
"reg_no",
"agent_name",
"assigned_to_name",
"insurer_name",
"insured_name",
"premium_amount",
"payment_mode",
"updated_on",
"status",
],
),
// SizedBox(width: 10),
// GestureDetector(
// onTap: () {
// // print('Export');
// },
// child: Container(
// padding: EdgeInsets.all(8.0),
// decoration: BoxDecoration(
// color: Color(0xFF425B5B),
// borderRadius: BorderRadius.circular(8.0),
// ),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(Icons.add, color: Colors.white),
//
// if (!ResponsiveLayout.isMobile(context)) ...[
// SizedBox(width: 10),
//
// GestureDetector(
// onTap: () {
// // context.go(AppRoutes.agent / create);
// context.go('/staff/create');
// },
// child: Text(
// 'Create New Staff',
// style: TextStyle(
// color: Colors.white,
// fontWeight: FontWeight.w600,
// fontSize: 14,
// ),
// ),
// ),
// ],
// ],
// ),
// ),
// ),
],
),
),
SizedBox(height: 10),
if (!ResponsiveLayout.isMobile(context))
Container(
decoration: BoxDecoration(
color: Color(0xFFEDF6F5),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
child: const Row(
children: [
Expanded(
flex: 2,
child: Text('Date', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Reg.No', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Agents', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Assigned To', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Insurer', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Insured Name', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Premium', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Payment Mode', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Policy Number', style: _headerStyle),
),
Expanded(
flex: 3,
child: Text('Status', style: _headerStyle),
),
Expanded(
flex: 1,
child: Text('Action', 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: ResponsiveLayout.isMobile(context)
? sortedData
.length // only cards for mobile
: sortedData.length + 1, // +1 for header in desktop
itemBuilder: (context, index) {
if (!ResponsiveLayout.isMobile(context) && index == 0) {
return _buildHeader();
}
final startIndex = (currentPage - 1) * itemsPerPage;
final item =
sortedData[index - (ResponsiveLayout.isMobile(context) ? 0 : 1)];
final sno = startIndex + index;
return !ResponsiveLayout.isMobile(context)
? _buildDataRow(item, sno)
: _buildDataCard(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: Color(0xFFEAEAEA), width: 1),
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(
flex: 2,
child: Text(
_formatDate(item['updated_on']) ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(item['reg_no'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(
_formatDate(item['agent_name']) ?? '-',
style: _dataBold,
),
),
Expanded(
flex: 2,
child: Text(item['assigned_to_name'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(
item['insurer_name'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
item['insured_name'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
item['premium_amount'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
item['payment_mode'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
item['policy_number'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 3,
child: Text(item['status'] ?? '-', style: _dataBold),
),
Expanded(
flex: 1,
child: Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(
Icons.more_vert,
color: Color(0xFF475569),
size: 14,
),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildPopupMenuActions(
context,
item,
item['id'],
item['reg_no'],
),
),
),
),
],
),
],
),
),
],
),
);
}
Widget _buildDataCard(Map<String, dynamic> item, int sno) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF6FEFD),
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: const Color(0xffD9EBE8)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Top row: Reg.No + Action menu
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(item['reg_no'] ?? '-', style: _headerStyle),
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: _buildPopupMenuActions(
context,
item,
item['id'],
item['reg_no'],
),
),
),
),
],
),
],
),
const Divider(color: Color(0xffD9EBE8), thickness: 0.8),
/// Company + Status
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Company", style: _cardheaderStyle),
Text(item['insurer_name'] ?? '-', style: _cardBodyStyle),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Status", style: _cardheaderStyle),
Text(item['status'] ?? '-', style: _cardBodyStyle),
],
),
),
],
),
const SizedBox(height: 15),
/// Agents + Assigned To
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Agent", style: _cardheaderStyle),
Text(item['agent_name'] ?? '-', style: _cardBodyStyle),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Assigned To", style: _cardheaderStyle),
Text(
item['assigned_to_name'] ?? '-',
style: _cardBodyStyle,
),
],
),
),
],
),
const SizedBox(height: 15),
/// Insured Name + Premium
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Insured Name", style: _cardheaderStyle),
Text(item['insured_name'] ?? '-', style: _cardBodyStyle),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Premium", style: _cardheaderStyle),
Text(item['premium_amount'] ?? '-', style: _cardBodyStyle),
],
),
),
],
),
const SizedBox(height: 15),
/// Payment Mode + Policy Number
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Payment Mode", style: _cardheaderStyle),
Text(item['payment_mode'] ?? '-', style: _cardBodyStyle),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Policy Number", style: _cardheaderStyle),
Text(item['policy_number'] ?? '-', style: _cardBodyStyle),
],
),
),
],
),
const SizedBox(height: 15),
/// Date + Remarks
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Date", style: _cardheaderStyle),
Text(
_formatDate(item['updated_on']) ?? '-',
style: _cardBodyStyle,
),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Remarks", style: _cardheaderStyle),
Text(
item['remarks'] ?? '-',
style: _cardBodyStyle,
maxLines: 3,
),
],
),
),
],
),
],
),
);
}
static final _dataBold = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Color(0xFF000000),
);
static final _dataSub = TextStyle(
fontSize: 10,
fontWeight: FontWeight.w300,
color: Color(0xFF585757),
);
static const _headerStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
);
static const _cardheaderStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w600,
fontSize: 12,
);
static const _cardBodyStyle = TextStyle(
color: Color(0xFF545454),
fontWeight: FontWeight.w400,
fontSize: 12,
);
}

View File

@ -0,0 +1,455 @@
import 'dart:convert';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:toastification/toastification.dart';
import '../../../../core/config/env.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/toastNotification.dart';
import '../../../../data/utils/validators.dart';
import '../../layouts/responsive_layout.dart';
import '../../themes/indicators/input_field_decoration.dart';
import '../../themes/indicators/text_field_theme.dart';
// 🔹 Custom Dialog Widget
class AssignStaffDialog extends StatefulWidget {
final dynamic userId;
final dynamic regNum;
final dynamic enquiryPrimaryId;
final void Function(String value) onSubmit;
const AssignStaffDialog({
super.key,
required this.onSubmit,
required this.regNum,
required this.userId,
required this.enquiryPrimaryId,
});
@override
State<AssignStaffDialog> createState() => _AddDialogState();
}
class _AddDialogState extends State<AssignStaffDialog> {
late ApiService apiService;
String? _token;
bool isLoading = false;
bool showSuccess = false;
late TextEditingController controller;
Map<String, TextEditingController> controllers = {};
final _formKey = GlobalKey<FormState>();
final _formKeyEndrosment = GlobalKey<FormState>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
List<String> tabHeader = ['regNum'];
List<Map<String, dynamic>> getStaffDetailsData = [];
List<Map<String, dynamic>> filteredStaffData = [];
String? selectedStaff;
String? selectedRegNum;
String? selectedStaffName;
Map<String, dynamic> dataDetails() {
final data = {
"id": widget.enquiryPrimaryId,
"assigned_to": selectedStaff,
"updated_by": widget.userId,
};
return data;
}
@override
void initState() {
super.initState();
apiService = ApiService();
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
controllers["regNum"]?.text = widget.regNum;
selectedRegNum = widget.regNum;
// 🔹 Init logic here (API calls, token fetch, etc.)
_initializeToken();
getStaffDetails();
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
Future<void> getStaffDetails() async {
print('getStaffDetails called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('Staffs');
if (response['status'] == 200) {
print('getStaffDetails - ${response['data']}');
setState(() {
getStaffDetailsData = List<Map<String, dynamic>>.from(
response['data'],
);
print('API Data - $getStaffDetailsData');
filteredStaffData = List.from(getStaffDetailsData);
print('originalData - $filteredStaffData');
});
} else {
getStaffDetailsData = [];
filteredStaffData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
void handleDone() {
if (!_formKey.currentState!.validate()) return;
setState(() {
if (_formKey.currentState!.validate()) {
dataDetails();
final dataSet = dataDetails();
print("dataSetAgent - $dataSet");
// print("managerId - $managerId ,userId - $userId ");
createUserData(dataSet);
} else {
// isDi sable = false;
}
});
}
Future<void> createUserData(data) async {
// final bool isUpdating = widget.id != null && widget.id != 'create';
final String apiUrldata;
apiUrldata = '${Env.apiUrl}enquiry/enquiryAssignUpdate';
// final token = await getToken(); // Fetch token
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
print("data------- $data}");
try {
final response = await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $_token',
'Content-Type': 'application/json',
'app-signature': 'nhance-partner-2025-signature-35468846JRhH551HK',
},
body: jsonEncode(data), // Convert map to JSON
);
if (response.statusCode == 200) {
print("Staff submitted successfully!");
print("Response: ${response.body}");
ToastHelper.showSuccessToast(context, 'Saved Successfully');
setState(() {
showSuccess = true;
});
// Navigator.of(context).pop();
//
widget.onSubmit("success");
// context.go(AppRoutes.staffLst);
} else {
final responseBody = jsonDecode(response.body);
dynamic msg = responseBody['data'];
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
ToastHelper.showErrorToast(context, 'Failed To Save');
}
} catch (e) {
print(" Error submitting Staff: $e");
}
}
@override
void dispose() {
// controllers.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
content: showSuccess
? Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
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: const Icon(Icons.close, size: 18),
),
),
],
),
const SizedBox(height: 16),
successContent(context),
],
)
: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Assign Vehicle Policy',
style: GoogleFonts.inter(
color: const Color(0xFF374141),
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
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: const Icon(Icons.close, size: 18),
),
),
],
),
const SizedBox(height: 16),
// 🔹 Switch content dynamically
claims(context),
],
),
actions: [
if (!showSuccess)
Center(
child: GestureDetector(
onTap: () {
handleDone();
// widget.onSubmit(controller.text.trim());
// Navigator.of(context).pop();
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 25.0,
vertical: 8,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: const Color(0xFF425B5B),
),
child: const Text(
'Assign Policy',
style: TextStyle(color: Colors.white),
),
),
),
),
],
);
}
// 🔹 Example: Claims widget
Widget claims(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
buildRegistrationNumber(context),
buildSelectStaffMem(context),
],
),
);
}
Widget successContent(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
"assets/miscellaneous/success_img1.png",
height: 60,
width: 60,
),
SizedBox(height: 15),
Text('Vehicle policy successfully assigned ', style: _successText),
Text(
'to $selectedStaffName for vehicle $selectedRegNum',
style: _successText,
),
],
);
}
// ------------------------- Widget Part -----------------------------------
Widget buildRegistrationNumber(context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Registration Number", style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['regNum']!,
backgroundColor: Color(0xFFEDF6F5),
// validator: (value) => Validators.requiredField(value, "name"),
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
readOnly: true,
),
],
);
}
Widget buildSelectStaffMem(ctx) {
// Map<String, dynamic>? selectedVehicle = filteredStaffData.firstWhere(
// (item) => item['id'].toString() == selectedStaff,
// orElse: () => {},
// );
Map<String, dynamic>? selectedVehicle;
try {
selectedVehicle = filteredStaffData.firstWhere(
(item) => item['id'].toString() == selectedStaff,
);
} catch (e) {
selectedVehicle = null; // fallback
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Select Staff Member", style: _textStyle),
SizedBox(height: 10),
Container(
color: Colors.white,
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
// height: 40,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKey,
// selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
selectedItem: selectedVehicle,
items: (filter, infiniteScrollProps) {
return filteredStaffData;
},
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;
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Staff Member",
).copyWith(
filled: true,
fillColor: Color(
0xFFEDF6F5,
), // 👈 makes the dropdown input white
),
),
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 ...",
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected Staff : ${val['name']}");
print("Id: ${val['id']}");
selectedStaffName = val['name'];
selectedStaff = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
// ------------------- STyle ---------------------------------
static final TextStyle _textStyle = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
);
static final TextStyle _successText = TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Color(0xFF177126),
);
}

File diff suppressed because it is too large Load Diff

View File

@ -4,17 +4,18 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import 'package:nhance_partner/presentation/screens/staff/assignStaff.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 '../../../providers/quotation_staff_proivder.dart';
import '../../../themes/indicators/export_btn.dart';
import '../../../themes/indicators/search_field_theme.dart';
import '../../../widgets/custom_action_popup.dart';
class policylist extends ConsumerStatefulWidget {
const policylist({super.key});
@override
@ -31,6 +32,7 @@ class policylistState extends ConsumerState<policylist> {
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
dynamic roleId;
@override
void initState() {
@ -40,7 +42,7 @@ class policylistState extends ConsumerState<policylist> {
Future.microtask(() {
final id = ref.read(managerIdProvider);
final roleId = ref.read(userRoleProvider);
roleId = ref.read(userRoleProvider);
print("ENQroleId - $roleId");
if (id != null) {
@ -49,6 +51,15 @@ class policylistState extends ConsumerState<policylist> {
});
}
void refresh() {
final id = ref.read(managerIdProvider);
final roleId = ref.read(userRoleProvider);
if (id != null) {
getStaffList(id, roleId);
}
}
Future<void> getStaffList(int managerId, role) async {
print('getClaimList called MagId - $managerId - $role');
setState(() {
@ -56,7 +67,10 @@ class policylistState extends ConsumerState<policylist> {
});
try {
final response = await apiService.fetchPolicyList(managerId, role);
final response = await apiService.fetchPolicyDataOnlyList(
managerId,
role,
);
if (response['status'] == 'success') {
final data = response['data'];
@ -94,6 +108,7 @@ class policylistState extends ConsumerState<policylist> {
final sortedData = [...filteredData]
..sort((a, b) => int.parse(b['id']) - int.parse(a['id']));
// final sortedData = [...filteredData];
final startIndex = (currentPage - 1) * itemsPerPage;
final endIndex = (currentPage * itemsPerPage).clamp(0, sortedData.length);
@ -107,8 +122,8 @@ class policylistState extends ConsumerState<policylist> {
// final isActiveStatus = item['is_active'] == "1" ? "active" : "inactive";
return (item['agent_name'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
query.toLowerCase(),
) ||
(item['reg_no'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
@ -127,10 +142,14 @@ class policylistState extends ConsumerState<policylist> {
(item['payment_mode'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['updated_on'] ?? '').toLowerCase().contains(
(_formatDate(item['updated_on']) ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['policy_number'] ?? '').toLowerCase().contains(query.toLowerCase());
(item['policy_number'] ?? '').toLowerCase().contains(
query.toLowerCase(),
) ||
(item['status'] ?? '').toLowerCase().contains(query.toLowerCase());
;
}).toList();
});
}
@ -146,24 +165,121 @@ class policylistState extends ConsumerState<policylist> {
}
}
List<Widget> _buildPopupMenuActions(BuildContext context, dynamic data) {
List<Widget> _buildPopupMenuActions(
BuildContext context,
dynamic data,
id,
regNum,
) {
return [
GestureDetector(
onTap: () {
Navigator.pop(context);
print('EDITStaff - ${data['id']}');
dynamic id = data['id'];
// context.go('/staff/$id');
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.edit_sharp, color: Color(0xFF319718), size: 18),
SizedBox(width: 10),
Text('Edit'),
],
if (roleId == 'manager') ...[
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
showDialog(
context: context,
builder: (ctx) => AssignStaffDialog(
enquiryPrimaryId: id,
regNum: regNum,
userId: 1,
onSubmit: (value) {
debugPrint("New assignY: $value");
refresh();
},
),
);
},
hoverColor: Color(0xFFE3F1F0),
splashColor: Color(0xFFE3F1F0),
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Row(
// mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
"assets/miscellaneous/image_3.png",
// height: 45,
// width: 15,
),
SizedBox(width: 10),
Text('Assign Staff'),
],
),
),
),
),
],
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
ref.read(quotationStaffIdProvider.notifier).state = id;
context.go(AppRoutes.quotation);
},
hoverColor: Color(0xFFE3F1F0),
splashColor: Color(0xFFE3F1F0),
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Row(
// mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
"assets/miscellaneous/image_1.png",
height: 15,
width: 15,
),
const SizedBox(width: 10),
((data['status'] == 'Policy Created') ||
(data['status'] == 'Quotation Accepted'))
? const Text('View Quotation')
: const Text('Create Quotation'),
],
),
),
),
),
if (data['status'] == 'Quotation Accepted' ||
data['status'] == 'Policy Created')
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.pop(context);
ref.read(quotationStaffIdProvider.notifier).state = id;
context.go(AppRoutes.policy);
},
hoverColor: Color(0xFFE3F1F0),
splashColor: Color(0xFFE3F1F0),
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Row(
// mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
"assets/miscellaneous/image_2.png",
height: 15,
width: 15,
),
const SizedBox(width: 10),
data['status'] == 'Policy Created'
? Text('View Policy')
: Text('Create Policy'),
],
),
),
),
),
];
}
@ -299,67 +415,65 @@ class policylistState extends ConsumerState<policylist> {
),
SizedBox(height: 10),
if (!ResponsiveLayout.isMobile(context))
Container(
decoration: BoxDecoration(
color: Color(0xFFEDF6F5),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
child: const Row(
children: [
Expanded(
flex: 2,
child: Text('Reg.No', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text(
'Agents',
style: _headerStyle,
Container(
decoration: BoxDecoration(
color: Color(0xFFEDF6F5),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
child: const Row(
children: [
Expanded(
flex: 2,
child: Text('Date', style: _headerStyle),
),
),
Expanded(
flex: 2,
child: Text('Assigned To', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Insurer', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Insured Name', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Premium', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Payment Mode', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Policy Number', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Date', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Status', style: _headerStyle),
),
Expanded(
flex: 1,
child: Text('Action', style: _headerStyle),
),
],
Expanded(
flex: 2,
child: Text('Reg.No', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Agents', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Assigned To', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Insurer', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Insured Name', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Premium', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Payment Mode', style: _headerStyle),
),
Expanded(
flex: 2,
child: Text('Policy Number', style: _headerStyle),
),
Expanded(
flex: 3,
child: Text('Status', style: _headerStyle),
),
// Expanded(
// flex: 1,
// child: Text('Action', style: _headerStyle),
// ),
],
),
),
),
Expanded(child: _buildDataTable(context)),
],
),
@ -407,7 +521,7 @@ class policylistState extends ConsumerState<policylist> {
return ListView.builder(
itemCount: ResponsiveLayout.isMobile(context)
? sortedData
.length // only cards for mobile
.length // only cards for mobile
: sortedData.length + 1, // +1 for header in desktop
itemBuilder: (context, index) {
if (!ResponsiveLayout.isMobile(context) && index == 0) {
@ -416,7 +530,7 @@ class policylistState extends ConsumerState<policylist> {
final startIndex = (currentPage - 1) * itemsPerPage;
final item =
sortedData[index - (ResponsiveLayout.isMobile(context) ? 0 : 1)];
sortedData[index - (ResponsiveLayout.isMobile(context) ? 0 : 1)];
final sno = startIndex + index;
return !ResponsiveLayout.isMobile(context)
@ -444,7 +558,19 @@ class policylistState extends ConsumerState<policylist> {
),
child: Row(
children: [
Expanded(flex: 2, child: Text('reg_no' ?? '-', style: _dataBold)),
Expanded(
flex: 2,
child: Text(
_formatDate(item['updated_on']) ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(item['reg_no'] ?? '-', style: _dataBold),
),
Expanded(
flex: 2,
child: Text(
@ -501,52 +627,50 @@ class policylistState extends ConsumerState<policylist> {
maxLines: 3,
),
),
Expanded(
flex: 2,
child: Text(
item['updated_on'] ?? '-',
style: _dataBold,
softWrap: true,
maxLines: 3,
),
),
Expanded(
flex: 2,
flex: 3,
child: Text(item['status'] ?? '-', style: _dataBold),
),
Expanded(
flex: 1,
child: Row(
children: [
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(
Icons.more_vert,
color: Color(0xFF475569),
size: 14,
),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
),
),
],
),
],
),
),
// Expanded(
// flex: 1,
// child: Row(
// children: [
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(
// Icons.more_vert,
// color: Color(0xFF475569),
// size: 14,
// ),
// itemBuilder: (context) => [
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 8,
// ),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: _buildPopupMenuActions(
// context,
// item,
// item['id'],
// item['reg_no'],
// ),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ),
],
),
);
@ -564,65 +688,84 @@ class policylistState extends ConsumerState<policylist> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Top row: Reg.No + Action menu
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(item['reg_no'] ?? '-', style: _headerStyle),
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: _buildPopupMenuActions(context, item),
),
),
),
],
),
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(Icons.more_vert, color: Color(0xFF475569), size: 14),
// itemBuilder: (context) => [
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: _buildPopupMenuActions(
// context,
// item,
// item['id'],
// item['reg_no'],
// ),
// ),
// ),
// ),
// ],
// ),
],
),
const Divider(color: Color(0xffD9EBE8), thickness: 0.8),
// Company + Vehicle Reg No
/// Company + Status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text("Company", style: _cardheaderStyle),
Text(
item['insurer_name'] ?? '-',
style: _cardBodyStyle,
maxLines: 3,
softWrap: true,
),
Text(item['insurer_name'] ?? '-', style: _cardBodyStyle),
],
),
),
SizedBox(width: 5),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const Text("Status", style: _cardheaderStyle),
Text(item['status'] ?? '-', style: _cardBodyStyle),
],
),
),
],
),
const SizedBox(height: 15),
/// Agents + Assigned To
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Agent", style: _cardheaderStyle),
Text(item['agent_name'] ?? '-', style: _cardBodyStyle),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Assigned To", style: _cardheaderStyle),
Text(
item['status'] ?? '-',
item['assigned_to_name'] ?? '-',
style: _cardBodyStyle,
maxLines: 2,
softWrap: true,
),
],
),
@ -631,23 +774,74 @@ class policylistState extends ConsumerState<policylist> {
),
const SizedBox(height: 15),
// Created On + Remarks
/// Insured Name + Premium
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Received Date & Time", style: _cardheaderStyle),
const Text("Insured Name", style: _cardheaderStyle),
Text(item['insured_name'] ?? '-', style: _cardBodyStyle),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Premium", style: _cardheaderStyle),
Text(item['premium_amount'] ?? '-', style: _cardBodyStyle),
],
),
),
],
),
const SizedBox(height: 15),
/// Payment Mode + Policy Number
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Payment Mode", style: _cardheaderStyle),
Text(item['payment_mode'] ?? '-', style: _cardBodyStyle),
],
),
),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Policy Number", style: _cardheaderStyle),
Text(item['policy_number'] ?? '-', style: _cardBodyStyle),
],
),
),
],
),
const SizedBox(height: 15),
/// Date + Remarks
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Date", style: _cardheaderStyle),
Text(
_formatDate(item['created_on']) ?? '-',
_formatDate(item['updated_on']) ?? '-',
style: _cardBodyStyle,
),
],
),
),
SizedBox(width: 5),
const SizedBox(width: 5),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -657,7 +851,6 @@ class policylistState extends ConsumerState<policylist> {
item['remarks'] ?? '-',
style: _cardBodyStyle,
maxLines: 3,
softWrap: true,
),
],
),

View File

@ -0,0 +1,604 @@
import 'dart:convert';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:toastification/toastification.dart';
import '../../../../core/config/env.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/toastNotification.dart';
import '../../../../data/utils/validators.dart';
import '../../../layouts/responsive_layout.dart';
import '../../../themes/indicators/customizd_file_upload.dart';
import '../../../themes/indicators/input_field_decoration.dart';
import '../../../themes/indicators/text_field_theme.dart';
// 🔹 Custom Dialog Widget
class createQuotatDialog extends StatefulWidget {
final dynamic userId;
final dynamic managerId;
final dynamic selectedQuotationFrmListId;
final dynamic selectedEnquiryId;
final Map<String, dynamic>? selectedQuotationFrmListdata;
final void Function(String value) onSubmit;
const createQuotatDialog({
super.key,
required this.onSubmit,
required this.userId,
this.selectedQuotationFrmListdata,
this.selectedQuotationFrmListId,
required this.managerId,
required this.selectedEnquiryId,
});
@override
State<createQuotatDialog> createState() => createQuotatDialogState();
}
class createQuotatDialogState extends State<createQuotatDialog> {
late ApiService apiService;
String? _token;
String? selectedFileNames;
bool isLoading = false;
late TextEditingController controller;
Map<String, TextEditingController> controllers = {};
final _formKey = GlobalKey<FormState>();
String? docUploadedFileUrlFromApi;
String? selectedId;
PlatformFile? docUploadedFile;
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
List<String> tabHeader = ['idv', 'premium_Amount'];
List<Map<String, dynamic>> getInsuranceTypeData = [];
List<Map<String, dynamic>> filteredInsuranceData = [];
String? selectedInsPlanType;
String? selectedEndorsement;
Map<String, dynamic> dataDetails() {
final data = {
"enquiry_id": widget.selectedEnquiryId,
"insured_declared_value": controllers["idv"]?.text,
"premium_amount": controllers["premium_Amount"]?.text,
"insurance_plan_type_id": selectedInsPlanType,
// "additional_uploaded_file_name": "extra_doc.pdf",
// "created_by": widget.userId,
"manager_id": widget.managerId,
};
return data;
}
@override
void initState() {
super.initState();
apiService = ApiService();
for (String field in tabHeader) {
controllers[field] = TextEditingController();
}
// 🔹 Init logic here (API calls, token fetch, etc.)
_initializeToken();
getInsuranceType();
updateData();
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
@override
void dispose() {
// Dispose all TextEditingControllers
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
void updateData() {
if (widget.selectedQuotationFrmListId != null &&
widget.selectedQuotationFrmListdata!.isNotEmpty) {
print('checkData -- ${widget.selectedQuotationFrmListdata}');
selectedId = widget.selectedQuotationFrmListId!;
// Pre-fill controllers
controllers["idv"]?.text =
widget.selectedQuotationFrmListdata!['insured_declared_value']
?.toString() ??
'';
controllers["premium_Amount"]?.text =
widget.selectedQuotationFrmListdata!['premium_amount']?.toString() ??
'';
selectedInsPlanType = widget
.selectedQuotationFrmListdata!['insurance_plan_type_id']
?.toString();
String? apiDocPath =
widget.selectedQuotationFrmListdata!["additional_uploaded_file_name"];
if (apiDocPath != null && apiDocPath.isNotEmpty) {
print('apiDocPath - $apiDocPath');
selectedFileNames = apiDocPath.split('/').last;
print('selectedFileNames - $selectedFileNames');
docUploadedFileUrlFromApi = apiDocPath;
print('passportFileUrlFromApi - $docUploadedFileUrlFromApi');
docUploadedFile = null;
} else {
selectedFileNames = null;
docUploadedFile = null;
docUploadedFileUrlFromApi = null;
}
}
}
Future<void> getInsuranceType() async {
print('getClaimList called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('InsuranceType');
if (response['status'] == 200) {
print('getInsuranceTypeData - ${response['data']}');
setState(() {
getInsuranceTypeData = List<Map<String, dynamic>>.from(
response['data'],
);
print('API Data - $getInsuranceTypeData');
filteredInsuranceData = List.from(getInsuranceTypeData);
print('originalData - $filteredInsuranceData');
});
} else {
getInsuranceTypeData = [];
filteredInsuranceData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
void handleDone() {
if (!_formKey.currentState!.validate()) return;
setState(() {
if (_formKey.currentState!.validate()) {
dataDetails();
final dataSet = dataDetails();
print("dataSetAgent - $dataSet");
// print("managerId - $managerId ,userId - $userId ");
createUserData(dataSet);
} else {
// isDi sable = false;
}
});
}
Future<void> createUserData(Map<String, dynamic> userData) async {
// final bool isUpdating = widget.selectedQuotationFrmListId != null;
// final id = widget.selectedQuotationFrmListId!;
final bool isUpdating = widget.selectedQuotationFrmListId != null;
final id = widget.selectedQuotationFrmListId; // keep nullable
print('id - $id');
final uri = Uri.parse(
// isUpdating
// ? 'https://venbait.in/nhance/partner/dev/api/agent/updateAgent'
// : 'https://venbait.in/nhance/partner/dev/api/agent/createAgent',
isUpdating
? '${Env.apiUrl}quotation/updateQuotation'
: '${Env.apiUrl}quotation/createQuotation',
);
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
// Use MultipartRequest (POST only)
final request = http.MultipartRequest('POST', uri);
request.headers['Authorization'] = 'Bearer $_token';
request.headers['app-signature'] =
'nhance-partner-2025-signature-35468846JRhH551HK';
// If updating, spoof the method Laravel-style
if (isUpdating) {
print('Updatrinf');
// request.fields['_method'] = 'PUT';
request.fields['id'] = id!;
request.fields['updated_by'] = widget.userId!.toString();
} else {
print('Not Updatrinf');
request.fields['created_by'] = widget.userId!.toString();
}
print("USerDAta - $userData");
// userData.forEach((key, value) {
// request.fields[key] = value.toString();
// print("✅ Encoded travel_details2: ${request.fields[key]}");
// });
userData.forEach((key, value) {
// if (key != 'certificate_file_name') {
request.fields[key] = value.toString();
print("✅ Encoded $key: ${request.fields[key]}");
// }
});
if (docUploadedFile != null) {
try {
if (docUploadedFile!.bytes != null) {
final multipartFile = http.MultipartFile.fromBytes(
'additional_uploaded_file_name',
docUploadedFile!.bytes!,
filename: docUploadedFile!.name,
);
request.files.add(multipartFile);
} else if (docUploadedFile!.path != null) {
final multipartFile = await http.MultipartFile.fromPath(
'additional_uploaded_file_name',
docUploadedFile!.path!,
filename: docUploadedFile!.name,
);
request.files.add(multipartFile);
}
print("📎 File attached: ${docUploadedFile!.name}");
} catch (e) {
print("❌ Failed to attach file: $e");
}
}
print(" Sending request with fields: ${request.fields}");
try {
final streamedResponse = await request.send();
final response = await http.Response.fromStream(streamedResponse);
print("Response status: ${response.statusCode}");
print("Response body: ${response.body}");
if (response.statusCode == 200 || response.statusCode == 201) {
// dispose();
print("✅ Agent submitted successfully!");
print("Response: ${response.body}");
widget.onSubmit('Success');
Navigator.of(context).pop();
} else {
final responseBody = jsonDecode(response.body);
dynamic msg = responseBody['data'];
print("❌ Submission failed. Status: ${response.statusCode}");
print("Body: ${response.body}");
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Agent User Creation Failed"),
content: Text(
msg,
// "There was a problem in creating user. Please try again.",
),
actions: [
TextButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
} catch (e) {
print("🔥 Error submitting user: $e");
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: Colors.white,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
widget.selectedQuotationFrmListId != null
? 'Update Quotation'
: 'Create Quotation',
style: GoogleFonts.inter(
color: const Color(0xFF374141),
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
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: const Icon(Icons.close, size: 18),
),
),
],
),
const SizedBox(height: 16),
// 🔹 Switch content dynamically
buildFormFields(context),
],
),
actions: [
GestureDetector(
onTap: () {
handleDone();
// widget.onSubmit(controller.text.trim());
// Navigator.of(context).pop();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 25.0, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
color: const Color(0xFF425B5B),
),
child: const Text('Save', style: TextStyle(color: Colors.white)),
),
),
],
);
}
Widget buildFormFields(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildIdv(context),
buildInsurancePlanType(context),
buildPremiumAmnt(context),
buildDocuments(context),
// if (!ResponsiveLayout.isMobile(context)) const SizedBox(height: 10),
// _buildResponsiveRow(
// context,
// buildAdditonalDocuments(context),
// buildRemarks(context),
// ),
],
),
);
}
// ------------------------- Claims Part -----------------------------------
Widget buildIdv(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('IDV *', style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['idv']!,
validator: (value) => Validators.doubleNumber(value, "IDV"),
// validator: (value) => Validators.number(value, "IDV"),
backgroundColor: Color(0xFFEDF6F5),
// readOnly: true,
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
),
],
);
}
Widget buildPremiumAmnt(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Premium Amount *', style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['premium_Amount']!,
backgroundColor: Color(0xFFEDF6F5),
validator: (value) => Validators.doubleNumber(value, "PremiumAmount"),
// validator: (value) => Validators.number(value, "PremiumAmount "),
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
),
],
);
}
Widget buildInsurancePlanType(context) {
Map<String, dynamic>? selectedVehicle = filteredInsuranceData.firstWhere(
(item) => item['id'].toString() == selectedInsPlanType,
orElse: () => {},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Plan Type *", style: _textStyle),
SizedBox(height: 10),
Container(
color: Colors.white,
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
// height: 40,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKey,
selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
items: (filter, infiniteScrollProps) {
return filteredInsuranceData;
},
itemAsString: (val) => val['insurance_plan_type'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
validator: (val) {
if (val == null) {
return "Required"; // error message
}
return null;
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Plan Type",
).copyWith(
filled: true,
fillColor: Color(
0xFFEDF6F5,
), // 👈 makes the dropdown input white
),
),
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 Plan Type...",
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected ClaimsType : ${val['insurance_plan_type']}");
print("Id: ${val['id']}");
selectedInsPlanType = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
Widget buildDocuments(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Quotation Documents ', style: _textStyle),
const SizedBox(height: 10),
Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ThemedUploadField(
hintText: selectedFileNames ?? "Upload Document",
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
backgroundColor: Color(0xFFEDF6F5),
onFileSelected: (fileName, file) {
print("Picked file: $fileName (${file.size} bytes)");
setState(() {
docUploadedFile = file;
});
},
),
const SizedBox(height: 5),
if (docUploadedFileUrlFromApi != null)
Container(
// color: Colors.white,
child: Row(
children: [
GestureDetector(
onTap: () => apiService.downloadFile(
apiUrl:
'api/quotation/downloadAdditionalUploadedFile?id=$selectedId',
apiId: selectedId,
localFile: docUploadedFile,
fileName: selectedFileNames,
),
child: Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Color(0xFF425B5B),
// color: Colors.green.shade300,
),
child: Row(
children: const [
Text(
"Download",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.white,
),
),
SizedBox(width: 5),
Icon(Icons.download, size: 13, color: Colors.white),
],
),
),
),
],
),
),
],
),
],
);
}
// ------------------- STyle ---------------------------------
static final TextStyle _textStyle = TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
);
}

File diff suppressed because it is too large Load Diff

View File

@ -57,6 +57,7 @@ class _ThemedDateFieldState extends State<ThemedDateField> {
}
}
@override
@override
Widget build(BuildContext context) {
return FormField<String>(
@ -64,48 +65,66 @@ class _ThemedDateFieldState extends State<ThemedDateField> {
initialValue: widget.controller?.text,
builder: (field) {
final hasError = field.hasError;
return Container(
width: widget.txtwidth ?? MediaQuery.of(context).size.width,
height: widget.txtheight,
child: InkWell(
onTap: () => pickDate(context, field),
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
decoration: BoxDecoration(
color: widget.backgroundColor ?? Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: hasError
? (widget.errorBorderColor ?? Colors.red)
: (widget.borderColor ?? Colors.grey.shade50),
width: 1.5,
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
onTap: () => pickDate(context, field),
borderRadius: BorderRadius.circular(10),
child: Container(
width: widget.txtwidth ?? MediaQuery.of(context).size.width,
height: widget.txtheight,
padding: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 15,
),
decoration: BoxDecoration(
color: widget.backgroundColor ?? Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: hasError
? (widget.errorBorderColor ?? Color(0xFFD83731))
: (widget.borderColor ?? Colors.grey.shade50),
width: 1,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
widget.controller?.text.isNotEmpty == true
? widget.controller!.text
: widget.hintText ?? "Select Date",
style: TextStyle(
fontSize: 13,
color: hasError ? Color(0xFFD83731) : Colors.black,
),
overflow: TextOverflow.ellipsis,
),
),
const Icon(
Icons.calendar_today,
size: 16,
color: Colors.black,
),
],
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
widget.controller?.text.isNotEmpty == true
? widget.controller!.text
: widget.hintText ?? "Select Date",
style: TextStyle(
fontSize: 12,
color: hasError ? Colors.red : Colors.black,
),
overflow: TextOverflow.ellipsis,
),
),
const Icon(
Icons.calendar_today,
size: 16,
color: Colors.black,
),
],
),
),
),
if (hasError)
Padding(
padding: const EdgeInsets.only(top: 5, left: 5),
child: Text(
field.errorText ?? '',
style: const TextStyle(
color: Color(0xFFD83731),
fontSize: 10,
),
),
),
],
);
},
);

View File

@ -79,17 +79,23 @@ class ExcelExporter {
required String fileName,
}) async {
try {
// Create Excel and delete default sheet
var excel = Excel.createExcel();
Sheet sheetObject = excel[sheetName];
if (excel.sheets.containsKey('Sheet1')) {
excel.delete('Sheet1');
}
// Create your sheet
Sheet sheetObject = excel[sheetName]; // now this is the only sheet
// Add headers
sheetObject.appendRow(headers.map((h) => TextCellValue(h)).toList());
// Add data
for (int i = 0; i < data.length; i++) {
for (var rowData in data) {
var row = <CellValue?>[];
for (var key in headers) {
final value = data[i][key];
final value = rowData[key];
if (value == null) {
row.add(TextCellValue('-'));
} else if (value is int) {
@ -106,7 +112,7 @@ class ExcelExporter {
var bytes = excel.encode()!;
if (kIsWeb) {
// Web: download as file
// Web: download
final blob = html.Blob([bytes]);
final url = html.Url.createObjectUrlFromBlob(blob);
final anchor = html.AnchorElement(href: url)
@ -114,7 +120,7 @@ class ExcelExporter {
..click();
html.Url.revokeObjectUrl(url);
} else {
// Mobile/Desktop: save to file system
// Mobile/Desktop: save file
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/$fileName.xlsx');
await file.writeAsBytes(bytes);
@ -125,3 +131,63 @@ class ExcelExporter {
}
}
}
// class ExcelExporter {
// static Future<void> exportToExcel({
// required String sheetName,
// required List<Map<String, dynamic>> data,
// required List<String> headers,
// required String fileName,
// }) async {
// try {
// // var excel = Excel.createExcel();
// // Sheet sheetObject = excel[sheetName];
//
// // Delete default sheet
// var excel = Excel.createExcel();
// excel.delete('Sheet1'); // remove default
// Sheet sheetObject = excel[sheetName];
//
// // Add headers
// sheetObject.appendRow(headers.map((h) => TextCellValue(h)).toList());
//
// // Add data
// for (int i = 0; i < data.length; i++) {
// var row = <CellValue?>[];
// for (var key in headers) {
// final value = data[i][key];
// if (value == null) {
// row.add(TextCellValue('-'));
// } else if (value is int) {
// row.add(IntCellValue(value));
// } else if (value is double) {
// row.add(DoubleCellValue(value));
// } else {
// row.add(TextCellValue(value.toString()));
// }
// }
// sheetObject.appendRow(row);
// }
//
// var bytes = excel.encode()!;
//
// if (kIsWeb) {
// // Web: download as file
// final blob = html.Blob([bytes]);
// final url = html.Url.createObjectUrlFromBlob(blob);
// final anchor = html.AnchorElement(href: url)
// ..setAttribute("download", "$fileName.xlsx")
// ..click();
// html.Url.revokeObjectUrl(url);
// } else {
// // Mobile/Desktop: save to file system
// final dir = await getApplicationDocumentsDirectory();
// final file = File('${dir.path}/$fileName.xlsx');
// await file.writeAsBytes(bytes);
// await OpenFile.open(file.path);
// }
// } catch (e) {
// print("Error exporting Excel: $e");
// }
// }
// }

View File

@ -103,7 +103,7 @@ class ThemedFormField extends HookWidget {
// dynamic hint color
color: const Color(0xFFC3C6CB),
),
// labelStyle: TextStyle(fontSize: 11, color: Colors.red),
labelStyle: TextStyle(fontSize: 12),
prefixIconConstraints: const BoxConstraints(
maxWidth: 25 + 16 + 10,
maxHeight: 25 + (8 * 2),

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../core/routing/routes.dart';
import 'package:go_router/go_router.dart';
@ -22,8 +23,10 @@ class DrawerMenuState extends ConsumerState<DrawerMenu> {
_overlayEntry = OverlayEntry(
builder: (context) {
return Positioned(
left: offset.dx + size.width,
top: offset.dy,
left: offset.dx + size.width - 5, // little spacing
top: offset.dy + (size.height / 2) - 20,
// left: offset.dx + size.width,
// top: offset.dy,
child: MouseRegion(
onExit: (_) => _hidePopup(),
child: Material(
@ -33,125 +36,144 @@ class DrawerMenuState extends ConsumerState<DrawerMenu> {
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: Colors.white,
color: const Color(0xFFDDF5F1),
// color: Colors.white,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
// mainAxisSize: MainAxisSize.min,
children: [
if (key == 'User') ...[
GestureDetector(
onTap: () {
print("Tappped Agent");
context.go(AppRoutes.agentLst);
},
child: const Text(
"Agent",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
_buildPopupItem(
label: "Agent",
onTap: () => context.go(AppRoutes.agentLst),
),
const SizedBox(height: 5),
GestureDetector(
onTap: () {
print("Bf clicked");
context.go(AppRoutes.staffLst);
},
child: const Text(
"Staff",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
_buildPopupItem(
label: "Staff",
onTap: () => context.go(AppRoutes.staffLst),
),
],
if (key == 'Reports') ...[
GestureDetector(
onTap: () {
context.go(AppRoutes.claimlist);
},
child: const Text(
"Claims",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
// const SizedBox(height: 5),
// GestureDetector(
// onTap: () {
// context.go(AppRoutes.enquiryLst);
// },
// child: const Text(
// "Enquiries",
// style: TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.w400,
// ),
// ),
// ),
const SizedBox(height: 5),
GestureDetector(
onTap: () {
context.go(AppRoutes.endosement);
},
child: const Text(
"Endorsement",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
_buildPopupItem(
label: "Claims",
onTap: () => context.go(AppRoutes.claimlist),
),
const SizedBox(height: 5),
GestureDetector(
onTap: () {
context.go(AppRoutes.policylist);
},
child: const Text(
"Policy",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
_buildPopupItem(
label: "Endorsement",
onTap: () => context.go(AppRoutes.endosement),
),
const SizedBox(height: 5),
GestureDetector(
onTap: () {
context.go(AppRoutes.quotation);
},
child: const Text(
"Quotation",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
),
const SizedBox(height: 5),
GestureDetector(
onTap: () {
context.go(AppRoutes.policy);
},
child: const Text(
"Policy Form",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
_buildPopupItem(
label: "Policy",
onTap: () => context.go(AppRoutes.policylist),
),
],
// if (key == 'User') ...[
// Row(
// children: [
// GestureDetector(
// onTap: () {
// print("Tappped Agent");
// context.go(AppRoutes.agentLst);
// },
// child: Text("Agent", style: _cardTextStyle),
// ),
//
// Icon(
// Icons.arrow_forward_ios_rounded,
// size: 12,
// color: Colors.black,
// ),
// ],
// ),
// const SizedBox(height: 5),
// Row(
// children: [
// GestureDetector(
// onTap: () {
// print("Bf clicked");
// context.go(AppRoutes.staffLst);
// },
// child: Text("Staff", style: _cardTextStyle),
// ),
//
// Icon(
// Icons.arrow_forward_ios_rounded,
// size: 12,
// color: Colors.black,
// ),
// ],
// ),
// ],
//
// if (key == 'Reports') ...[
// GestureDetector(
// onTap: () {
// context.go(AppRoutes.claimlist);
// },
// child: Row(
// children: [
// Text("Claims", style: _cardTextStyle),
// Icon(
// Icons.arrow_forward_ios_rounded,
// size: 12,
// color: Colors.black,
// ),
// ],
// ),
// ),
// // const SizedBox(height: 5),
// // GestureDetector(
// // onTap: () {
// // context.go(AppRoutes.enquiryLst);
// // },
// // child: const Text(
// // "Enquiries",
// // style: TextStyle(
// // fontSize: 16,
// // fontWeight: FontWeight.w400,
// // ),
// // ),
// // ),
// const SizedBox(height: 5),
// GestureDetector(
// onTap: () {
// context.go(AppRoutes.endosement);
// },
// child: Row(
// children: [
// Text("Endorsement", style: _cardTextStyle),
//
// Icon(
// Icons.arrow_forward_ios_rounded,
// size: 12,
// color: Colors.black,
// ),
// ],
// ),
// ),
// const SizedBox(height: 5),
// GestureDetector(
// onTap: () {
// context.go(AppRoutes.policylist);
// },
// child: Row(
// children: [
// Text("Policy", style: _cardTextStyle),
//
// Icon(
// Icons.arrow_forward_ios_rounded,
// size: 12,
// color: Colors.black,
// ),
// ],
// ),
// ),
// ],
],
),
),
@ -183,11 +205,16 @@ class DrawerMenuState extends ConsumerState<DrawerMenu> {
padding: EdgeInsets.zero,
children: [
SizedBox(height: 30),
DrawerContentWrapper(
child: Image.asset(
"assets/drawer/drawerImg1.png",
height: 25,
width: 25,
GestureDetector(
onTap: () {
context.go(AppRoutes.dashboard);
},
child: DrawerContentWrapper(
child: Image.asset(
"assets/drawer/drawerImg1.png",
height: 25,
width: 25,
),
),
),
DrawerLabel("Dashboard"),
@ -215,24 +242,16 @@ class DrawerMenuState extends ConsumerState<DrawerMenu> {
},
),
DrawerLabel("Reports"),
// DrawerContentWrapper(
// child: IconButton(
// onPressed: () {
// // context.go('/tabEnquiry/tab');
// ref.read(enquiryIdProvider.notifier).state = 'tab';
// context.go(AppRoutes.tabEnquiry);
// },
// icon: Icon(Icons.list_alt_rounded, size: 30, color: Colors.black),
// ),
// ),
DrawerContentWrapper(
child: Consumer(
builder: (context, ref, _) {
return IconButton(
onPressed: () {
if (roleId == 'manager') {
} else if (roleId == 'agent') {
print('roleI - $roleId');
if (roleId == 'agent') {
context.go(AppRoutes.enquiryLst);
} else {
context.go(AppRoutes.enquiryForStaff);
}
// ref.read(enquiryIdProvider.notifier).state = null;
// context.go(AppRoutes.tabEnquiry);
@ -277,6 +296,15 @@ class DrawerMenuState extends ConsumerState<DrawerMenu> {
}
}
Widget _buildPopupItem({required String label, required VoidCallback onTap}) {
return _HoverableContainer(
label: label,
onTap: onTap,
normalColor: Color(0xFFDDF5F1),
hoverColor: Colors.white, // change to your desired hover color
);
}
class DrawerLabel extends StatelessWidget {
final String text;
const DrawerLabel(this.text, {super.key});
@ -286,9 +314,71 @@ class DrawerLabel extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(text, style: const TextStyle(color: Colors.white, fontSize: 11.5)),
Text(
text,
style: GoogleFonts.inter(color: Colors.white, fontSize: 11.5),
),
SizedBox(height: 20),
],
);
}
}
class _HoverableContainer extends StatefulWidget {
final String label;
final VoidCallback onTap;
final Color normalColor;
final Color hoverColor;
const _HoverableContainer({
super.key,
required this.label,
required this.onTap,
required this.normalColor,
required this.hoverColor,
});
@override
_HoverableContainerState createState() => _HoverableContainerState();
}
class _HoverableContainerState extends State<_HoverableContainer> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: GestureDetector(
onTap: widget.onTap,
child: Container(
decoration: BoxDecoration(
color: _isHovered ? widget.hoverColor : widget.normalColor,
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: 110,
child: Text(widget.label, style: _cardTextStyle),
),
const Icon(
Icons.arrow_forward_ios_rounded,
size: 12,
color: Colors.black,
),
],
),
),
),
);
}
}
TextStyle _cardTextStyle = GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w500,
);

View File

@ -1,23 +1,102 @@
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';
class MobileTabs extends StatefulWidget {
final ValueChanged<int> onTabChanged;
const MobileTabs({super.key, required this.onTabChanged});
import '../../core/routing/routes.dart';
import '../providers/userRoleProvider.dart';
class MobileBottomMenu extends ConsumerStatefulWidget {
const MobileBottomMenu({super.key});
@override
State<MobileTabs> createState() => _MobileTabsState();
ConsumerState<MobileBottomMenu> createState() => _MobileBottomMenuState();
}
class _MobileTabsState extends State<MobileTabs> {
class _MobileBottomMenuState extends ConsumerState<MobileBottomMenu> {
int _currentIndex = 0;
void _showOptionsSheet(List<Map<String, dynamic>> options) {
showModalBottomSheet(
context: context,
backgroundColor: const Color(0xFFDDF5F1),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (_) => ListView.separated(
padding: const EdgeInsets.all(16),
shrinkWrap: true,
itemCount: options.length,
separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (_, index) {
final item = options[index];
return ListTile(
title: Text(
item['label'],
style: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
onTap: () {
Navigator.pop(context);
context.go(item['route']);
},
);
},
),
);
}
@override
Widget build(BuildContext context) {
final roleId = ref.watch(userRoleProvider);
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: const Color(0xFF425B5B),
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white70,
currentIndex: _currentIndex,
onTap: (index) {
setState(() => _currentIndex = index);
widget.onTabChanged(index);
switch (index) {
case 0:
context.go(AppRoutes.dashboard);
break;
case 1:
// Reports tapped show bottom sheet
_showOptionsSheet([
{'label': 'Claims', 'route': AppRoutes.claimlist},
{'label': 'Endorsement', 'route': AppRoutes.endosement},
{'label': 'Policy', 'route': AppRoutes.policylist},
]);
break;
case 2:
// Enquiry
if (roleId == 'agent') {
context.go(AppRoutes.enquiryLst);
} else {
context.go(AppRoutes.enquiryForStaff);
}
break;
case 3:
// User options only for manager
if (roleId == 'manager') {
_showOptionsSheet([
{'label': 'Agent', 'route': AppRoutes.agentLst},
{'label': 'Staff', 'route': AppRoutes.staffLst},
]);
} else {
context.go(AppRoutes.profile);
}
break;
}
},
items: [
BottomNavigationBarItem(
@ -28,18 +107,23 @@ class _MobileTabsState extends State<MobileTabs> {
),
label: "Dashboard",
),
const BottomNavigationBarItem(
icon: Icon(Icons.person),
label: "Profile",
),
BottomNavigationBarItem(
icon: Image.asset(
"assets/drawer/drawerImg2.png",
height: 20,
width: 20,
),
label: "Reports",
),
const BottomNavigationBarItem(
icon: Icon(Icons.list_alt_rounded, size: 24, color: Colors.black),
label: "Enquiry",
),
const BottomNavigationBarItem(
icon: Icon(Icons.person_add_alt, size: 24, color: Colors.black),
label: "User",
),
],
);
}