staff enq modification

This commit is contained in:
venbaittech 2025-11-13 18:10:21 +05:30
parent 857179ca65
commit 7b0e14c1ba
28 changed files with 9525 additions and 1378 deletions

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@ import 'package:nhance_partner/core/routing/routes.dart';
import 'package:nhance_partner/presentation/screens/Enquiry/enquiryList.dart'; import 'package:nhance_partner/presentation/screens/Enquiry/enquiryList.dart';
import 'package:nhance_partner/presentation/screens/UserManagement/Profile/profile_web.dart'; import 'package:nhance_partner/presentation/screens/UserManagement/Profile/profile_web.dart';
import 'package:nhance_partner/presentation/screens/dashboard/dashboard.dart'; import 'package:nhance_partner/presentation/screens/dashboard/dashboard.dart';
import 'package:nhance_partner/presentation/screens/handler/enquiryList.dart';
import '../../data/services/auth_service.dart'; import '../../data/services/auth_service.dart';
import '../../presentation/providers/manager_provider.dart'; import '../../presentation/providers/manager_provider.dart';
import '../../presentation/screens/Enquiry/enquiry/tabs.dart'; import '../../presentation/screens/Enquiry/enquiry/tabs.dart';
@ -18,9 +18,12 @@ import '../../presentation/screens/UserManagement/Agent/agentList.dart';
import '../../presentation/screens/UserManagement/Profile/profile_mobile.dart'; import '../../presentation/screens/UserManagement/Profile/profile_mobile.dart';
import '../../presentation/screens/UserManagement/Staff/staff.dart'; import '../../presentation/screens/UserManagement/Staff/staff.dart';
import '../../presentation/screens/UserManagement/Staff/staffList.dart'; import '../../presentation/screens/UserManagement/Staff/staffList.dart';
import '../../presentation/screens/handler/enquiryList.dart';
import '../../presentation/screens/handler/enquiryListOld.dart';
import '../../presentation/screens/home/home_screen.dart'; import '../../presentation/screens/home/home_screen.dart';
import '../../presentation/screens/login/login_screen.dart'; import '../../presentation/screens/login/login_screen.dart';
import '../../presentation/screens/splash/splash_screen.dart'; import '../../presentation/screens/splash/splash_screen.dart';
import '../../presentation/screens/staff/Enquiry/enquiry_inline_list.dart';
import '../../presentation/screens/staff/Enquiry/enquiry_list.dart'; import '../../presentation/screens/staff/Enquiry/enquiry_list.dart';
import '../../presentation/screens/staff/policy/policy.dart'; import '../../presentation/screens/staff/policy/policy.dart';
import '../../presentation/screens/staff/policy/policy_list.dart'; import '../../presentation/screens/staff/policy/policy_list.dart';
@ -157,11 +160,13 @@ final GoRouter appRouter = GoRouter(
GoRoute( GoRoute(
path: AppRoutes.enquiryForStaff, path: AppRoutes.enquiryForStaff,
builder: (context, state) => const EnquiryStaff(), builder: (context, state) => const EnquiryListStaffInline(),
// builder: (context, state) => const EnquiryStaff(),
), ),
GoRoute( GoRoute(
path: AppRoutes.enquiryHandlerLst, path: AppRoutes.enquiryHandlerLst,
builder: (context, state) => const EnquiryHandler(), // builder: (context, state) => const EnquiryHandler(),
builder: (context, state) => EnquiryListHandler(),
), ),
GoRoute( GoRoute(
path: AppRoutes.allStaffAttendance, path: AppRoutes.allStaffAttendance,

View File

@ -596,13 +596,14 @@ class ApiService {
final String query; final String query;
if (role == 'manager' || role == 'handler') { // if (role == 'manager' || role == 'handler') {
query = 'manager_id=$id'; // query = 'manager_id=$id';
}
// else if (role == 'handler') {
// query = 'handler_id=$id';
// } // }
else if (role == 'staff') { if (role == 'manager') {
query = 'manager_id=$id';
} else if (role == 'handler') {
query = 'handler_id=$id';
} else if (role == 'staff') {
query = 'staff_id=$id'; query = 'staff_id=$id';
} else { } else {
query = 'agent_id=$id'; query = 'agent_id=$id';
@ -991,6 +992,8 @@ class ApiService {
url = Uri.parse('${Env.apiUrl}master/getClaimMaster'); url = Uri.parse('${Env.apiUrl}master/getClaimMaster');
} else if (val == 'Endorsement') { } else if (val == 'Endorsement') {
url = Uri.parse('${Env.apiUrl}master/getEndorsementMaster'); url = Uri.parse('${Env.apiUrl}master/getEndorsementMaster');
} else if (val == 'Broker') {
url = Uri.parse('${Env.apiUrl}master/getAllBrokers');
} }
final headers = { final headers = {

View File

@ -50,68 +50,73 @@ class PaginationControls extends StatelessWidget {
final int totalPages = (totalItems / itemsPerPage).ceil(); final int totalPages = (totalItems / itemsPerPage).ceil();
final List<int> pages = _visiblePages(totalPages, currentPage); final List<int> pages = _visiblePages(totalPages, currentPage);
return Row( return Container(
mainAxisAlignment: MainAxisAlignment.end, // color: Colors.yellow.shade100,
children: [ height: 30,
// Dropdown for rows per page child: Row(
DropdownButton<int>( mainAxisAlignment: MainAxisAlignment.end,
value: itemsPerPage, mainAxisSize: MainAxisSize.min,
items: [5, 10, 15, 20, 50].map((int value) { children: [
return DropdownMenuItem<int>( // Dropdown for rows per page
value: value, DropdownButton<int>(
child: Text('$value', style: GoogleFonts.poppins(fontSize: 15)), value: itemsPerPage,
); items: [5, 10, 15, 20, 50].map((int value) {
}).toList(), return DropdownMenuItem<int>(
onChanged: (newValue) { value: value,
if (newValue != null) { child: Text('$value', style: GoogleFonts.inter(fontSize: 12)),
onItemsPerPageChanged(newValue); );
onPageChanged(1); }).toList(),
} onChanged: (newValue) {
}, if (newValue != null) {
), onItemsPerPageChanged(newValue);
onPageChanged(1);
}
},
),
// Previous Button // Previous Button
IconButton( IconButton(
onPressed: currentPage > 1 onPressed: currentPage > 1
? () => onPageChanged(currentPage - 1) ? () => onPageChanged(currentPage - 1)
: null, : null,
icon: const Icon(Icons.chevron_left), icon: const Icon(Icons.chevron_left, size: 18),
), ),
// Page buttons with ellipsis // Page buttons with ellipsis
for (final i in pages) for (final i in pages)
if (i == -1) if (i == -1)
const Padding( const Padding(
padding: EdgeInsets.symmetric(horizontal: 4), padding: EdgeInsets.symmetric(horizontal: 2),
child: Text("..."), child: Text("..."),
) )
else else
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 2),
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: currentPage == i backgroundColor: currentPage == i
? const Color(0xFFD4F8F3) ? const Color(0xFFD4F8F3)
: const Color(0xFFEDF6F5), : const Color(0xFFEDF6F5),
foregroundColor: currentPage == i foregroundColor: currentPage == i
? Colors.black ? Colors.black
: Colors.grey, : Colors.grey,
minimumSize: const Size(36, 36), minimumSize: const Size(26, 26),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
),
onPressed: () => onPageChanged(i),
child: Text(i.toString()),
), ),
onPressed: () => onPageChanged(i),
child: Text(i.toString()),
), ),
),
// Next Button // Next Button
IconButton( IconButton(
onPressed: currentPage < totalPages onPressed: currentPage < totalPages
? () => onPageChanged(currentPage + 1) ? () => onPageChanged(currentPage + 1)
: null, : null,
icon: const Icon(Icons.chevron_right), icon: const Icon(Icons.chevron_right, size: 18),
), ),
], ],
),
); );
} }
} }

View File

@ -33,7 +33,7 @@ class MainLayout extends ConsumerStatefulWidget {
class MainLayoutState extends ConsumerState<MainLayout> { class MainLayoutState extends ConsumerState<MainLayout> {
dynamic role; dynamic role;
ApiService apiService = ApiService(); ApiService apiService = ApiService();
bool isDrawerOpen = true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -93,7 +93,12 @@ class MainLayoutState extends ConsumerState<MainLayout> {
appBar: TopBar( appBar: TopBar(
title: widget.title, title: widget.title,
onMenuPressed: () { onMenuPressed: () {
Scaffold.of(context).openDrawer(); // Scaffold.of(context).openDrawer();
// Scaffold.of(context).openDrawer();
setState(() {
isDrawerOpen = !isDrawerOpen;
});
}, },
onLogout: () async { onLogout: () async {
debugPrint("Logout tapped"); debugPrint("Logout tapped");
@ -118,7 +123,7 @@ class MainLayoutState extends ConsumerState<MainLayout> {
items: [ items: [
PopupMenuItem( PopupMenuItem(
enabled: false, enabled: false,
child: SizedBox(width: 500, child: ProfilePopUp()), child: SizedBox(width: 80, child: ProfilePopUp()),
), ),
], ],
); );
@ -130,13 +135,17 @@ class MainLayoutState extends ConsumerState<MainLayout> {
), ),
body: Row( body: Row(
children: [ children: [
const SizedBox( AnimatedContainer(
width: 100, // fixed width for drawer duration: const Duration(milliseconds: 250),
child: DrawerMenu(), width: isDrawerOpen ? 100 : 0, // 👈 adjust width
child: isDrawerOpen
? const DrawerMenu()
: const SizedBox.shrink(), // hides drawer
), ),
Expanded( Expanded(
child: Container( child: Container(
padding: EdgeInsets.all(2.0), padding: const EdgeInsets.all(8.0),
color: Colors.white, color: Colors.white,
child: Column(children: [Expanded(child: widget.body)]), child: Column(children: [Expanded(child: widget.body)]),
), ),
@ -144,6 +153,23 @@ class MainLayoutState extends ConsumerState<MainLayout> {
], ],
), ),
), ),
// body: Row(
// children: [
// const SizedBox(
// width: 100, // fixed width for drawer
// child: DrawerMenu(),
// ),
// Expanded(
// child: Container(
// padding: EdgeInsets.all(2.0),
// color: Colors.white,
// child: Column(children: [Expanded(child: widget.body)]),
// ),
// ),
// ],
// ),
// ),
); );
} }
} }

View File

@ -5,6 +5,7 @@ final managerIdProvider = StateProvider<int?>((ref) => null);
final userIdProvider = StateProvider<int?>((ref) => null); final userIdProvider = StateProvider<int?>((ref) => null);
final enquiryIdProvider = StateProvider<String?>((ref) => null); final enquiryIdProvider = StateProvider<String?>((ref) => null);
final navFromEnqStaffProvider = StateProvider<String?>((ref) => null); final navFromEnqStaffProvider = StateProvider<String?>((ref) => null);
final enqBrokerNameProvider = StateProvider<String?>((ref) => null);
final staffIndiviualAttendanceIdProvider = StateProvider<String?>( final staffIndiviualAttendanceIdProvider = StateProvider<String?>(
(ref) => null, (ref) => null,
); );

View File

@ -86,12 +86,12 @@ class EnquiryTabState extends ConsumerState<EnquiryTab> {
String? selectedInsurer; String? selectedInsurer;
String? selectedAgent; String? selectedAgent;
Map<String, TextEditingController> controllers = {};
String? _token; String? _token;
dynamic userId; dynamic userId;
dynamic managerId; dynamic managerId;
dynamic role; dynamic role;
Map<String, TextEditingController> controllers = {};
List<Map<String, dynamic>> getVehicleTypeData = []; List<Map<String, dynamic>> getVehicleTypeData = [];
List<Map<String, dynamic>> filteredVechicleData = []; List<Map<String, dynamic>> filteredVechicleData = [];
@ -733,42 +733,6 @@ class EnquiryTabState extends ConsumerState<EnquiryTab> {
} }
} }
// Widget buildResponsiveUploadField({
// required String label,
// required String? hintText,
// required void Function(String fileName, dynamic file) onFileSelected,
// })
// {
// final isMobile = ResponsiveLayout.isMobile(context);
//
// final uploadWidget = ThemedUploadField(
// hintText: hintText ?? "Upload Document",
// txtwidth: MediaQuery.of(context).size.width * 0.26,
// onFileSelected: onFileSelected,
// );
//
// if (isMobile) {
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(label, style: _textStyle),
// const SizedBox(height: 8),
// uploadWidget,
// const SizedBox(height: 16),
// ],
// );
// } else {
// return Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Expanded(child: Text(label, style: _textStyle)),
// const SizedBox(width: 10),
// uploadWidget,
// ],
// );
// }
// }
Widget buildName(BuildContext context) { Widget buildName(BuildContext context) {
return buildResponsiveField( return buildResponsiveField(
label: "Insured Name *", label: "Insured Name *",

View File

@ -677,7 +677,8 @@ class StaffState extends ConsumerState<Staff> {
(item) => item['id'].toString() == selectedHandler, (item) => item['id'].toString() == selectedHandler,
orElse: () => {}, orElse: () => {},
); );
final isReadOnly = widget.id != null; // final isReadOnly = widget.id != null;
final isReadOnly = false;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
class InlineEditableTable extends StatefulWidget {
const InlineEditableTable({super.key});
@override
State<InlineEditableTable> createState() => _InlineEditableTableState();
}
class _InlineEditableTableState extends State<InlineEditableTable> {
List<Map<String, dynamic>> rows = [
{"id": 1, "agent_name": "Ravi", "insurer_name": "ICICI"},
{"id": 2, "agent_name": "Priya", "insurer_name": "HDFC"},
];
Map<int, bool> isEditingRow = {};
Map<int, TextEditingController> agentControllers = {};
Map<int, TextEditingController> insurerControllers = {};
void _addRow() {
final newId = DateTime.now().millisecondsSinceEpoch;
setState(() {
rows.add({"id": newId, "agent_name": "", "insurer_name": ""});
isEditingRow[newId] = true;
agentControllers[newId] = TextEditingController();
insurerControllers[newId] = TextEditingController();
});
}
void _editRow(int id) {
setState(() {
isEditingRow[id] = true;
agentControllers[id] = TextEditingController(
text: rows.firstWhere((row) => row["id"] == id)["agent_name"],
);
insurerControllers[id] = TextEditingController(
text: rows.firstWhere((row) => row["id"] == id)["insurer_name"],
);
});
}
void _saveRow(int id) {
setState(() {
final index = rows.indexWhere((row) => row["id"] == id);
if (index != -1) {
rows[index]["agent_name"] = agentControllers[id]?.text ?? "";
rows[index]["insurer_name"] = insurerControllers[id]?.text ?? "";
}
isEditingRow[id] = false;
});
}
void _deleteRow(int id) {
setState(() {
rows.removeWhere((row) => row["id"] == id);
isEditingRow.remove(id);
agentControllers.remove(id);
insurerControllers.remove(id);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Inline Editable Table")),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: ElevatedButton.icon(
onPressed: _addRow,
icon: const Icon(Icons.add),
label: const Text("Add Row"),
),
),
const SizedBox(height: 10),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columns: const [
DataColumn(label: Text("Agent Name")),
DataColumn(label: Text("Insurer Name")),
DataColumn(label: Text("Actions")),
],
rows: rows.map((row) {
final id = row["id"];
final isEditing = isEditingRow[id] ?? false;
return DataRow(
cells: [
DataCell(
isEditing
? TextField(
controller: agentControllers[id],
decoration: const InputDecoration(
isDense: true,
border: OutlineInputBorder(),
),
)
: Text(row["agent_name"]),
),
DataCell(
isEditing
? TextField(
controller: insurerControllers[id],
decoration: const InputDecoration(
isDense: true,
border: OutlineInputBorder(),
),
)
: Text(row["insurer_name"]),
),
DataCell(
Row(
children: [
if (isEditing)
IconButton(
icon: const Icon(
Icons.save,
color: Colors.green,
),
onPressed: () => _saveRow(id),
)
else
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.blue,
),
onPressed: () => _editRow(id),
),
IconButton(
icon: const Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () => _deleteRow(id),
),
],
),
),
],
);
}).toList(),
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,515 @@
import 'dart:convert';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import '../../../../core/config/env.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/toastNotification.dart';
import '../../layouts/responsive_layout.dart';
import '../../providers/manager_provider.dart';
class QuotationPopUpTab extends ConsumerStatefulWidget {
String? id;
final Future<void> Function()? onRefresh;
QuotationPopUpTab({super.key, this.id, this.onRefresh});
@override
ConsumerState<QuotationPopUpTab> createState() => QuotationTabState();
}
class QuotationTabState extends ConsumerState<QuotationPopUpTab> {
late ApiService apiService;
bool isLoading = false;
bool blockKey = false;
String? _token;
dynamic userId;
dynamic managerId;
dynamic role;
List<Map<String, dynamic>> getQuotationData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
@override
void initState() {
super.initState();
apiService = ApiService();
_initializeToken();
Future.microtask(() {
managerId = ref.watch(managerIdProvider);
userId = ref.watch(userIdProvider);
role = ref.watch(userRoleProvider);
getQuotationList();
});
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
void refresh() {
getQuotationList();
}
/// ✅ Instead of using `widget.data`, call the API directly
Future<void> getQuotationList() async {
print('Fetching quotation list from API...');
setState(() => isLoading = true);
try {
if (widget.id == null) {
throw Exception('QuotationTab: ID is null');
}
// Example: adjust this endpoint based on your actual API
final response = await apiService.findEnqQuotePolicyView(widget.id);
// Extract quotation data safely
final quotations = (response["data"]?["quotations"] as List?) ?? [];
final data = quotations
.map((e) => Map<String, dynamic>.from(e as Map))
.toList();
setState(() {
getQuotationData = data;
filteredData = List.from(data);
blockKey = data.any((item) => item['status'] == 'Accepted');
});
print('Fetched quotation data: ${data.length}');
} catch (e, s) {
print('❌ Error fetching quotations: $e\n$s');
} finally {
setState(() => isLoading = false);
}
}
Future<void> handleAction(String action, String quotationId) async {
final String apiUrldata = '${Env.apiUrl}quotation/acceptOrRejectQuotation';
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
final Map<String, dynamic> data = {
"id": quotationId,
"status": action,
"action_by": userId,
"action_user": role,
};
print("data------- $data");
try {
final response = await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $_token',
'Content-Type': 'application/json',
'app-signature': Env.App_Signature,
},
body: jsonEncode(data),
);
if (response.statusCode == 200) {
print("Response: ${response.body}");
ToastHelper.showSuccessToast(context, 'Status Updated');
// Close popup
Navigator.of(context).pop();
// Refresh parent
if (widget.onRefresh != null) {
await widget.onRefresh!();
}
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
final responseBody = jsonDecode(response.body);
ToastHelper.showErrorToast(context, 'Status Updation Failed');
print("Failed to submit. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print("Error submitting: $e");
}
}
void _showQuotationPopup() {
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Container(
width: ResponsiveLayout.isMobile(context)
? MediaQuery.of(context).size.width * 0.9
: MediaQuery.of(context).size.width * 0.5,
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Quotation Details',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
],
),
const SizedBox(height: 20),
Flexible(
child: SingleChildScrollView(
child: Column(
children: [
...filteredData
.map((item) => _buildQuotationCard(item))
.toList(),
],
),
),
),
],
),
),
);
},
);
}
Widget _buildQuotationCard(Map<String, dynamic> item) {
final bool isAccepted = item['status'] == 'Accepted';
final bool isRejected = item['status'] == 'Rejected';
final bool isPending = item['status'] == 'Pending';
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isAccepted
? Colors.green.withOpacity(0.1)
: isRejected
? Colors.red.withOpacity(0.1)
: const Color(0xFFF6FEFD),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isAccepted
? Colors.green
: isRejected
? Colors.red
: const Color(0xffD9EBE8),
width: 1.5,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item['insurer_name'] ?? '-',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: _getStatusColor(item['status']),
borderRadius: BorderRadius.circular(12),
),
child: Text(
item['status'] ?? '-',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 12),
// Details
Row(
children: [
Expanded(
child: _buildDetailItem(
'IDV',
item['insured_declared_value']?.toString() ?? '-',
),
),
Expanded(
child: _buildDetailItem(
'Premium',
item['premium_amount']?.toString() ?? '-',
),
),
],
),
const SizedBox(height: 8),
_buildDetailItem(
'Plan Type',
item['insurance_plan_type']?.toString() ?? '-',
),
// Action buttons (only if pending and no other quotation is accepted)
if (isPending && !blockKey) ...[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => handleAction('Rejected', item['id']),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
backgroundColor: Colors.red.shade50,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Reject',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: () => handleAction('Accepted', item['id']),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
backgroundColor: const Color(0xFF425B5B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Accept',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
// Show message if blocked
if (isPending && blockKey) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(Icons.info_outline, size: 16, color: Colors.orange),
SizedBox(width: 8),
Expanded(
child: Text(
'Another quotation has been accepted',
style: TextStyle(fontSize: 12, color: Colors.orange),
),
),
],
),
),
],
],
),
);
}
Widget _buildDetailItem(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label: ',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF545454),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
),
],
),
);
}
Color _getStatusColor(String? status) {
switch (status) {
case 'Pending':
return Colors.orange;
case 'Accepted':
return Colors.green;
case 'Rejected':
return Colors.red;
default:
return Colors.grey;
}
}
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(16),
child: isLoading
? const Center(child: CircularProgressIndicator())
: filteredData.isNotEmpty
? Column(
children: [
// Button to open popup
ElevatedButton.icon(
onPressed: _showQuotationPopup,
icon: const Icon(Icons.visibility),
label: Text('View Quotations (${filteredData.length})'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF425B5B),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
const SizedBox(height: 20),
// Summary cards
Expanded(
child: GridView.count(
crossAxisCount: ResponsiveLayout.isMobile(context) ? 1 : 3,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: ResponsiveLayout.isMobile(context)
? 3
: 2,
children: [
_buildSummaryCard(
'Total Quotations',
filteredData.length.toString(),
Icons.description,
Colors.blue,
),
_buildSummaryCard(
'Accepted',
filteredData
.where((e) => e['status'] == 'Accepted')
.length
.toString(),
Icons.check_circle,
Colors.green,
),
_buildSummaryCard(
'Pending',
filteredData
.where((e) => e['status'] == 'Pending')
.length
.toString(),
Icons.pending,
Colors.orange,
),
],
),
),
],
)
: const Center(child: Text('No Available Data')),
);
}
Widget _buildSummaryCard(
String title,
String count,
IconData icon,
Color color,
) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 32, color: color),
const SizedBox(height: 8),
Text(
count,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: color,
),
),
const SizedBox(height: 4),
Text(
title,
style: TextStyle(
fontSize: 14,
color: color,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
],
),
);
}
}

View File

@ -0,0 +1,582 @@
import 'dart:convert';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import '../../../../core/config/env.dart';
import '../../../../core/services/api_service.dart';
import '../../../../data/services/auth_service.dart';
import '../../../../data/utils/toastNotification.dart';
import '../../layouts/responsive_layout.dart';
import '../../providers/manager_provider.dart';
class QuotationPopUpTab extends ConsumerStatefulWidget {
String? id;
final Future<void> Function()? onRefresh;
QuotationPopUpTab({super.key, this.id, this.onRefresh});
@override
ConsumerState<QuotationPopUpTab> createState() => QuotationTabState();
}
class QuotationTabState extends ConsumerState<QuotationPopUpTab> {
late ApiService apiService;
bool isLoading = false;
bool blockKey = false;
String? _token;
dynamic userId;
dynamic managerId;
dynamic role;
List<Map<String, dynamic>> getQuotationData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
@override
void initState() {
super.initState();
apiService = ApiService();
_initializeToken();
Future.microtask(() {
managerId = ref.watch(managerIdProvider);
userId = ref.watch(userIdProvider);
role = ref.watch(userRoleProvider);
getQuotationList();
});
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
void refresh() {
getQuotationList();
}
/// Instead of using `widget.data`, call the API directly
Future<void> getQuotationList() async {
print('Fetching quotation list from API...');
setState(() => isLoading = true);
try {
if (widget.id == null) {
throw Exception('QuotationTab: ID is null');
}
// Example: adjust this endpoint based on your actual API
final response = await apiService.findEnqQuotePolicyView(widget.id);
// Extract quotation data safely
final quotations = (response["data"]?["quotations"] as List?) ?? [];
final data = quotations
.map((e) => Map<String, dynamic>.from(e as Map))
.toList();
setState(() {
getQuotationData = data;
filteredData = List.from(data);
blockKey = data.any((item) => item['status'] == 'Accepted');
});
print('Fetched quotation data: ${data.length}');
} catch (e, s) {
print('❌ Error fetching quotations: $e\n$s');
} finally {
setState(() => isLoading = false);
}
}
Future<void> handleAction(String action, String quotationId) async {
final String apiUrldata = '${Env.apiUrl}quotation/acceptOrRejectQuotation';
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
final Map<String, dynamic> data = {
"id": quotationId,
"status": action,
"action_by": userId,
"action_user": role,
};
print("data------- $data");
try {
final response = await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $_token',
'Content-Type': 'application/json',
'app-signature': Env.App_Signature,
},
body: jsonEncode(data),
);
if (response.statusCode == 200) {
print("Response: ${response.body}");
ToastHelper.showSuccessToast(context, 'Status Updated');
// Close popup
Navigator.of(context).pop();
// Refresh parent
if (widget.onRefresh != null) {
await widget.onRefresh!();
}
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
final responseBody = jsonDecode(response.body);
ToastHelper.showErrorToast(context, 'Status Updation Failed');
print("Failed to submit. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print("Error submitting: $e");
}
}
Widget _buildQuotationPopupContent() {
return Container(
// width: ResponsiveLayout.isMobile(context)
// ? MediaQuery.of(context).size.width * 0.9
// : MediaQuery.of(context).size.width * 0.5,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
// border: Border.all(color: const Color(0xFFD9EBE8)),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.05),
// blurRadius: 8,
// offset: const Offset(0, 4),
// ),
// ],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Quotation Details',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.close),
),
],
),
const SizedBox(height: 5),
Flexible(
child: SingleChildScrollView(
child: _buildQuotationTable(), // Only one table
),
),
],
),
);
}
Widget _buildQuotationTable() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFD9EBE8)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🔹 Table Header
Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
color: const Color(0xFFEFF6F5),
child: Row(
children: const [
Expanded(
flex: 1,
child: Text(
"IDV",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
"Plan Type",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 1,
child: Text(
"Premium",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 3,
child: Center(
child: Text(
"Action",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
],
),
),
const Divider(height: 1, color: Color(0xFFD9EBE8)),
// 🔹 Table Rows
...filteredData.map((item) {
final bool isAccepted = item['status'] == 'Accepted';
final bool isRejected = item['status'] == 'Rejected';
final bool isPending = item['status'] == 'Pending';
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey.withOpacity(0.2)),
),
color: isAccepted
? Colors.green.withOpacity(0.05)
: isRejected
? Colors.red.withOpacity(0.05)
: Colors.white,
),
child: Row(
children: [
Expanded(
flex: 1,
child: Text(
item['insured_declared_value']?.toString() ?? '-',
style: const TextStyle(fontSize: 14),
),
),
Expanded(
flex: 2,
child: Text(
item['insurance_plan_type']?.toString() ?? '-',
style: const TextStyle(fontSize: 14),
),
),
Expanded(
flex: 1,
child: Text(
item['premium_amount']?.toString() ?? '-',
style: const TextStyle(fontSize: 14),
),
),
Expanded(
flex: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (isPending && !blockKey) ...[
TextButton(
onPressed: () =>
handleAction('Rejected', item['id']),
style: TextButton.styleFrom(
backgroundColor: Colors.red.shade50,
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
),
child: const Text(
'Reject',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () =>
handleAction('Accepted', item['id']),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF425B5B),
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
),
child: const Text(
'Accept',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
],
if (isAccepted)
const Text(
'Accepted',
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
if (isRejected)
const Text(
'Rejected',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
);
}),
],
),
);
}
Widget _buildQuotationCard(Map<String, dynamic> item) {
final bool isAccepted = item['status'] == 'Accepted';
final bool isRejected = item['status'] == 'Rejected';
final bool isPending = item['status'] == 'Pending';
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isAccepted
? Colors.green.withOpacity(0.1)
: isRejected
? Colors.red.withOpacity(0.1)
: const Color(0xFFF6FEFD),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isAccepted
? Colors.green
: isRejected
? Colors.red
: const Color(0xffD9EBE8),
width: 1.5,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item['insurer_name'] ?? '-',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: _getStatusColor(item['status']),
borderRadius: BorderRadius.circular(12),
),
child: Text(
item['status'] ?? '-',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 12),
// Details
Row(
children: [
Expanded(
child: _buildDetailItem(
'IDV',
item['insured_declared_value']?.toString() ?? '-',
),
),
Expanded(
child: _buildDetailItem(
'Premium',
item['premium_amount']?.toString() ?? '-',
),
),
],
),
const SizedBox(height: 8),
_buildDetailItem(
'Plan Type',
item['insurance_plan_type']?.toString() ?? '-',
),
// Action buttons (only if pending and no other quotation is accepted)
if (isPending && !blockKey) ...[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => handleAction('Rejected', item['id']),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
backgroundColor: Colors.red.shade50,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Reject',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: () => handleAction('Accepted', item['id']),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
backgroundColor: const Color(0xFF425B5B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Accept',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
// Show message if blocked
if (isPending && blockKey) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(Icons.info_outline, size: 16, color: Colors.orange),
SizedBox(width: 8),
Expanded(
child: Text(
'Another quotation has been accepted',
style: TextStyle(fontSize: 12, color: Colors.orange),
),
),
],
),
),
],
],
),
);
}
Widget _buildDetailItem(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label: ',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF545454),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
),
],
),
);
}
Color _getStatusColor(String? status) {
switch (status) {
case 'Pending':
return Colors.orange;
case 'Accepted':
return Colors.green;
case 'Rejected':
return Colors.red;
default:
return Colors.grey;
}
}
@override
@override
Widget build(BuildContext context) {
return Container(
// height: MediaQuery.of(context).size.height,
// width: MediaQuery.of(context).size.width,
// padding: const EdgeInsets.all(16),
child: isLoading
? const Center(child: CircularProgressIndicator())
: filteredData.isNotEmpty
? SingleChildScrollView(
child: Column(
children: [
_buildQuotationPopupContent(), // now renders inline
const SizedBox(height: 20),
],
),
)
: const Center(child: Text('No Available Data')),
);
}
}

View File

@ -263,9 +263,9 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
final message = data['data']['message']; final message = data['data']['message'];
// setState(() { setState(() {
// enteredEmailOrMobile = true; enteredEmailOrMobile = true;
// }); });
if (verification == true) { if (verification == true) {
// if (switcherStatus == 1) { // if (switcherStatus == 1) {

File diff suppressed because it is too large Load Diff

View File

@ -1084,10 +1084,6 @@ class EnquiryStaffState extends ConsumerState<EnquiryStaff> {
color: Colors.white, color: Colors.white,
child: InkWell( child: InkWell(
hoverColor: Color(0xFFEAF6F4), hoverColor: Color(0xFFEAF6F4),
// hoverColor: const Color(0xffD9EBE8),
// focusColor: const Color(0xffD9EBE8),
// splashColor: const Color(0xffD9EBE8),
// highlightColor: const Color(0xffD9EBE8),
onTap: () async { onTap: () async {
// Get overlay and button position RELATIVE to InkWell // Get overlay and button position RELATIVE to InkWell
final RenderBox button = final RenderBox button =

File diff suppressed because it is too large Load Diff

View File

@ -165,6 +165,7 @@ class QuotationStaffTabState extends ConsumerState<QuotationStaffTab> {
void updateEnquiryData(enquiryData) { void updateEnquiryData(enquiryData) {
print('updateEnquiryData - $enquiryData'); print('updateEnquiryData - $enquiryData');
if (!mounted) return;
setState(() { setState(() {
selectedInsurdId = enquiryData['insurer_id']; selectedInsurdId = enquiryData['insurer_id'];
selectedEnquiryId = enquiryData['id']; selectedEnquiryId = enquiryData['id'];
@ -192,13 +193,14 @@ class QuotationStaffTabState extends ConsumerState<QuotationStaffTab> {
Future<void> getQuotationList(enqQuotation) async { Future<void> getQuotationList(enqQuotation) async {
print('getClaimList called enqQuotation- $enqQuotation'); print('getClaimList called enqQuotation- $enqQuotation');
if (!mounted) return;
setState(() { setState(() {
isLoading = true; isLoading = true;
}); });
try { try {
final response = await apiService.findEnqQuotePolicyView(enqQuotation); final response = await apiService.findEnqQuotePolicyView(enqQuotation);
if (!mounted) return;
if (response['status'] == 'success') { if (response['status'] == 'success') {
print('quoationListData - ${response['data']}'); print('quoationListData - ${response['data']}');
setState(() { setState(() {
@ -226,6 +228,7 @@ class QuotationStaffTabState extends ConsumerState<QuotationStaffTab> {
} catch (e) { } catch (e) {
print('Exception occurred: $e'); print('Exception occurred: $e');
} finally { } finally {
if (!mounted) return;
setState(() { setState(() {
isLoading = false; isLoading = false;
}); });
@ -266,7 +269,8 @@ class QuotationStaffTabState extends ConsumerState<QuotationStaffTab> {
// scrollDirection: Axis.vertical, // scrollDirection: Axis.vertical,
child: Column( child: Column(
children: [ children: [
if (!blockKey && roleId != 'manager') ...[ if (!blockKey) ...[
// if (!blockKey && roleId != 'manager') ...[
CreateQuotationForm( CreateQuotationForm(
key: ValueKey(selectedQuotationFrmListId ?? "new"), key: ValueKey(selectedQuotationFrmListId ?? "new"),
userId: userId, userId: userId,

View File

@ -39,6 +39,7 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
dynamic selectedInsuredName; dynamic selectedInsuredName;
dynamic selectedVehicleNum; dynamic selectedVehicleNum;
dynamic selectedVehicleType; dynamic selectedVehicleType;
dynamic selectedBrokerName;
@override @override
void initState() { void initState() {
@ -97,8 +98,9 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
selectedInsuredName = data['enquiry']?['name']; selectedInsuredName = data['enquiry']?['name'];
selectedVehicleNum = data['enquiry']?['reg_no']; selectedVehicleNum = data['enquiry']?['reg_no'];
selectedVehicleType = data['enquiry']?['vehicle_type']; selectedVehicleType = data['enquiry']?['vehicle_type'];
selectedBrokerName = data['enquiry']?['broker_name'];
print( print(
'loadQuotationTab 4s - $selectedInsuredName - $selectedVehicleNum - $selectedVehicleType', 'loadQuotationTab 4s -$selectedBrokerName $selectedInsuredName - $selectedVehicleNum - $selectedVehicleType',
); );
tabs = [ tabs = [
@ -126,6 +128,24 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
expandedIndex = selectedIndex; expandedIndex = selectedIndex;
}); });
if (selectedBrokerName == 'Nhance') {
print("Chooese name Nhance");
final prefs = await SharedPreferences.getInstance();
await prefs.remove('enqBrokerNameProvider');
// Save the new id
await prefs.setString(
'enqBrokerNameProvider',
selectedBrokerName.toString(),
);
ref.read(enqBrokerNameProvider.notifier).state = selectedBrokerName;
} else {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('enqBrokerNameProvider');
ref.read(enqBrokerNameProvider.notifier).state = null;
}
} }
/// Public method to load data and select a tab /// Public method to load data and select a tab
@ -146,9 +166,8 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white, backgroundColor: Colors.white,
content: Container( content: Container(
width: MediaQuery.of(context).size.width * 0.6, width: MediaQuery.of(context).size.width * 0.65,
height: MediaQuery.of(context).size.height * 0.8, height: MediaQuery.of(context).size.height * 0.9,
child: isLoading child: isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: tabs.isEmpty : tabs.isEmpty
@ -211,6 +230,7 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
insured_name(context), insured_name(context),
vehcile_num(context), vehcile_num(context),
vehcile_Type(context), vehcile_Type(context),
broker(context),
SizedBox.shrink(), SizedBox.shrink(),
], ],
), ),
@ -310,6 +330,24 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
); );
} }
Widget broker(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Broker', style: _textStyle),
SizedBox(height: 10),
Text(selectedBrokerName ?? '', style: _textDataStyle),
// ThemedFormField(
// controller: controllers['insurer']!,
// readOnly: true,
// txtwidth: ResponsiveLayout.isMobile(context)
// ? null
// : MediaQuery.of(context).size.width * 0.2,
// ),
],
);
}
static final _textStyle = GoogleFonts.inter( static final _textStyle = GoogleFonts.inter(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View File

@ -51,19 +51,26 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
List<Map<String, dynamic>> getInsurersData = []; List<Map<String, dynamic>> getInsurersData = [];
List<Map<String, dynamic>> filteredInsurersData = []; List<Map<String, dynamic>> filteredInsurersData = [];
String? selectedInsurer; String? selectedInsurer;
List<Map<String, dynamic>> getBrokerData = [];
List<Map<String, dynamic>> filteredBrokerData = [];
String? selectedBroker;
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _formKeyEndrosment = GlobalKey<FormState>(); final _formKeyEndrosment = GlobalKey<FormState>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey = final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>(); GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownKeyInsurer = GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
List<String> tabHeader = ['regNum']; List<String> tabHeader = ['regNum'];
List<Map<String, dynamic>> getStaffDetailsData = []; final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
List<Map<String, dynamic>> filteredStaffData = []; dropDownKeyInsurerEnqAsgn =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyBroker =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
List<Map<String, dynamic>> getStaffDetailsDataEnqAsgn = [];
List<Map<String, dynamic>> filteredStaffDataEnqAsgn = [];
String? selectedStaff; String? selectedStaff;
String? selectedRegNum; String? selectedRegNum;
@ -74,6 +81,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
"id": widget.enquiryPrimaryId, "id": widget.enquiryPrimaryId,
"assigned_to": selectedStaff, "assigned_to": selectedStaff,
"insurer_id": selectedInsurer, "insurer_id": selectedInsurer,
"broker_id": selectedBroker,
"updated_by": widget.userId, "updated_by": widget.userId,
}; };
return data; return data;
@ -92,17 +100,19 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
// 🔹 Init logic here (API calls, token fetch, etc.) // 🔹 Init logic here (API calls, token fetch, etc.)
_initializeToken(); _initializeToken();
// getStaffDetails(1); // getStaffDetailsForEnquiryAssignment(1);
getInsurers(); getInsurers();
getBroker();
Future.microtask(() { Future.microtask(() {
final managerId = ref.watch(managerIdProvider); final managerId = ref.watch(managerIdProvider);
final userID = ref.watch(userIdProvider);
// final handlerId = ref.watch(handlerIdProvider); // final handlerId = ref.watch(handlerIdProvider);
role = ref.watch(userRoleProvider); role = ref.watch(userRoleProvider);
final userID = ref.watch(userIdProvider);
print("managerId - $managerId"); print("managerId - $managerId");
if (userID != null) { if (userID != null) {
print('hansles'); print('hansles');
getStaffDetails(userID); getStaffDetailsForEnquiryAssignment(userID);
} }
}); });
} }
@ -143,8 +153,39 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
} }
} }
Future<void> getStaffDetails(int id) async { Future<void> getBroker() async {
print('getStaffDetails called By handler'); print('getBroker called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchMasterDropDown('Broker');
if (response['status'] == 200) {
print('getBroker - ${response['data']}');
setState(() {
getBrokerData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getBrokerData');
filteredBrokerData = List.from(getBrokerData);
print('originalData - $filteredBrokerData');
});
} else {
getBrokerData = [];
filteredBrokerData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getStaffDetailsForEnquiryAssignment(int id) async {
print('getStaffDetailsForEnquiryAssignment called By handler');
setState(() { setState(() {
isLoading = true; isLoading = true;
}); });
@ -157,19 +198,19 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
); );
if (response['status'] == 'success') { if (response['status'] == 'success') {
print('getStaffDetails - ${response['data']}'); print('getStaffDetailsForEnquiryAssignment - ${response['data']}');
setState(() { setState(() {
getStaffDetailsData = List<Map<String, dynamic>>.from( getStaffDetailsDataEnqAsgn = List<Map<String, dynamic>>.from(
response['data'], response['data'],
); );
print('API Data - $getStaffDetailsData'); print('API Data - $getStaffDetailsDataEnqAsgn');
filteredStaffData = List.from(getStaffDetailsData); filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn);
print('originalData - $filteredStaffData'); print('originalData - $filteredStaffDataEnqAsgn');
}); });
} else { } else {
getStaffDetailsData = []; getStaffDetailsDataEnqAsgn = [];
filteredStaffData = []; filteredStaffDataEnqAsgn = [];
} }
} catch (e) { } catch (e) {
print('Exception occurred: $e'); print('Exception occurred: $e');
@ -363,6 +404,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
buildRegistrationNumber(context), buildRegistrationNumber(context),
buildSelectStaffMem(context), buildSelectStaffMem(context),
buildInsurer(context), buildInsurer(context),
buildBroker(context),
], ],
), ),
); );
@ -409,14 +451,14 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
} }
Widget buildSelectStaffMem(ctx) { Widget buildSelectStaffMem(ctx) {
// Map<String, dynamic>? selectedVehicle = filteredStaffData.firstWhere( // Map<String, dynamic>? selectedVehicle = filteredStaffDataEnqAsgn.firstWhere(
// (item) => item['id'].toString() == selectedStaff, // (item) => item['id'].toString() == selectedStaff,
// orElse: () => {}, // orElse: () => {},
// ); // );
Map<String, dynamic>? selectedVehicle; Map<String, dynamic>? selectedVehicle;
try { try {
selectedVehicle = filteredStaffData.firstWhere( selectedVehicle = filteredStaffDataEnqAsgn.firstWhere(
(item) => item['id'].toString() == selectedStaff, (item) => item['id'].toString() == selectedStaff,
); );
} catch (e) { } catch (e) {
@ -439,7 +481,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
// selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
selectedItem: selectedVehicle, selectedItem: selectedVehicle,
items: (filter, infiniteScrollProps) { items: (filter, infiniteScrollProps) {
return filteredStaffData; return filteredStaffDataEnqAsgn;
}, },
itemAsString: (val) => val['name'].toString(), itemAsString: (val) => val['name'].toString(),
@ -490,6 +532,19 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
), ),
), ),
// constraints: BoxConstraints(), // constraints: BoxConstraints(),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 14, color: Colors.black),
),
);
},
), ),
onChanged: (val) { onChanged: (val) {
@ -528,7 +583,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
: MediaQuery.of(context).size.width * 0.26, : MediaQuery.of(context).size.width * 0.26,
// height: 40, // height: 40,
child: DropdownSearch<Map<String, dynamic>>( child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyInsurer, key: dropDownKeyInsurerEnqAsgn,
selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null, selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null,
items: (filter, infiniteScrollProps) { items: (filter, infiniteScrollProps) {
return filteredInsurersData; return filteredInsurersData;
@ -581,6 +636,19 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
), ),
), ),
), ),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 14, color: Colors.black),
),
);
},
// constraints: BoxConstraints(), // constraints: BoxConstraints(),
), ),
@ -599,6 +667,111 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
); );
} }
Widget buildBroker(BuildContext context) {
Map<String, dynamic>? selectedBrokers = filteredBrokerData.firstWhere(
(item) => item['id'].toString() == selectedBroker,
orElse: () => {},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Broker *', style: _textStyle),
SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
// borderRadius: BorderRadius.circular(10.0),
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
// height: 40,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyBroker,
selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null,
items: (filter, infiniteScrollProps) {
return filteredBrokerData;
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
validator: (val) {
if (val == null) {
return "Required"; // error message
}
return null;
},
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Broker",
).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 Broker...",
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 14, color: Colors.black),
),
);
},
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected Broker : ${val['name']}");
print("Id: ${val['id']}");
selectedBroker = val['id'];
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
),
),
],
);
}
// ------------------- STyle --------------------------------- // ------------------- STyle ---------------------------------
static final TextStyle _textStyle = TextStyle( static final TextStyle _textStyle = TextStyle(

View File

@ -1,8 +1,157 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:google_fonts/google_fonts.dart';
import 'fileUploadService.dart'; import 'fileUploadService.dart';
class ThemedUploadField extends StatefulWidget {
final String? hintText;
final double? txtwidth;
final double? txtheight;
final Color? backgroundColor;
final Color? borderColor;
final Color? errorBorderColor;
final Function(String fileName, PlatformFile file)? onFileSelected;
FormFieldValidator<String>? validator;
final List<String>? allowedExtensions;
final bool isTxtBtnCase;
final String? txtName;
final double? padVertical;
final double? padHorizontal;
ThemedUploadField({
super.key,
this.hintText,
this.txtwidth,
this.txtheight,
this.backgroundColor,
this.borderColor,
this.errorBorderColor,
this.onFileSelected,
this.validator,
this.allowedExtensions,
this.isTxtBtnCase = false,
this.txtName,
this.padVertical,
this.padHorizontal,
});
@override
State<ThemedUploadField> createState() => _ThemedUploadFieldState();
}
class _ThemedUploadFieldState extends State<ThemedUploadField> {
final fileService = FileUploadService();
String? selectedFileName;
String? errorMessage;
Future<void> _pickFile() async {
final error = await fileService.pickSingleFile(
maxFileSizeInMB: 3,
allowedExtensions: widget.allowedExtensions,
);
if (error != null) {
setState(() => errorMessage = error);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(error)));
} else if (fileService.singleFile != null) {
setState(() {
selectedFileName = fileService.singleFile!.name;
errorMessage = null;
});
widget.onFileSelected?.call(
fileService.singleFile!.name,
fileService.singleFile!,
);
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: widget.txtwidth ?? MediaQuery.of(context).size.width,
height: widget.txtheight,
child: DecoratedBox(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
child: InkWell(
onTap: _pickFile,
borderRadius: BorderRadius.circular(10),
child: Container(
padding: EdgeInsets.symmetric(
vertical: widget.padVertical ?? 10,
horizontal: widget.padHorizontal ?? 15,
),
decoration: BoxDecoration(
color: widget.backgroundColor ?? Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: errorMessage != null
? (widget.errorBorderColor ?? Colors.red)
: (widget.borderColor ??
widget.backgroundColor ??
Colors.grey.shade50),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.isTxtBtnCase) ...[
Expanded(
child: Text(
selectedFileName ??
widget.hintText ??
"Upload Document",
style: TextStyle(
fontSize: 12,
color: errorMessage != null
? Colors.red
: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
),
const Icon(
Icons.file_upload_outlined,
color: Colors.black,
),
],
if (widget.isTxtBtnCase) ...[
Center(
child: Tooltip(
message: 'Upload Documents',
child: Text(
widget.txtName!,
style: GoogleFonts.inter(fontSize: 12),
),
),
),
],
],
),
),
),
),
),
if (errorMessage != null)
Padding(
padding: const EdgeInsets.only(left: 8, top: 4),
child: Text(
errorMessage!,
style: const TextStyle(color: Colors.red, fontSize: 10),
),
),
],
);
}
}
// class ThemedUploadField extends FormField<String> { // class ThemedUploadField extends FormField<String> {
// ThemedUploadField({ // ThemedUploadField({
// Key? key, // Key? key,
@ -98,124 +247,3 @@ import 'fileUploadService.dart';
// }, // },
// ); // );
// } // }
class ThemedUploadField extends StatefulWidget {
final String? hintText;
final double? txtwidth;
final double? txtheight;
final Color? backgroundColor;
final Color? borderColor;
final Color? errorBorderColor;
final Function(String fileName, PlatformFile file)? onFileSelected;
FormFieldValidator<String>? validator;
final List<String>? allowedExtensions;
ThemedUploadField({
super.key,
this.hintText,
this.txtwidth,
this.txtheight,
this.backgroundColor,
this.borderColor,
this.errorBorderColor,
this.onFileSelected,
this.validator,
this.allowedExtensions,
});
@override
State<ThemedUploadField> createState() => _ThemedUploadFieldState();
}
class _ThemedUploadFieldState extends State<ThemedUploadField> {
final fileService = FileUploadService();
String? selectedFileName;
String? errorMessage;
Future<void> _pickFile() async {
final error = await fileService.pickSingleFile(
maxFileSizeInMB: 3,
allowedExtensions: widget.allowedExtensions,
);
if (error != null) {
setState(() => errorMessage = error);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(error)));
} else if (fileService.singleFile != null) {
setState(() {
selectedFileName = fileService.singleFile!.name;
errorMessage = null;
});
widget.onFileSelected?.call(
fileService.singleFile!.name,
fileService.singleFile!,
);
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: widget.txtwidth ?? MediaQuery.of(context).size.width,
height: widget.txtheight,
child: DecoratedBox(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
child: InkWell(
onTap: _pickFile,
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: errorMessage != null
? (widget.errorBorderColor ?? Colors.red)
: (widget.borderColor ?? Colors.grey.shade50),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
selectedFileName ??
widget.hintText ??
"Upload Document",
style: TextStyle(
fontSize: 12,
color: errorMessage != null
? Colors.red
: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.file_upload_outlined, color: Colors.black),
],
),
),
),
),
),
if (errorMessage != null)
Padding(
padding: const EdgeInsets.only(left: 8, top: 4),
child: Text(
errorMessage!,
style: const TextStyle(color: Colors.red, fontSize: 10),
),
),
],
);
}
}

View File

@ -80,21 +80,21 @@ class ExportBtn extends HookWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
?txt! // ?txt!
? Text( // ? Text(
'Export', // 'Export',
style: TextStyle( // style: TextStyle(
color: Colors.white, // color: Colors.white,
fontWeight: FontWeight.w600, // fontWeight: FontWeight.w600,
fontSize: 14, // fontSize: 14,
), // ),
) // )
: null, // : null,
?txt! ? SizedBox(width: 15) : null, // ?txt! ? SizedBox(width: 15) : null,
Image.asset( Image.asset(
"assets/miscellaneous/export.png", "assets/miscellaneous/export.png",
height: 25, height: 15,
width: 25, width: 15,
), ),
// Icon(Icons.input_sharp, color: Colors.white), // Icon(Icons.input_sharp, color: Colors.white),
// Image.asset("assets/miscellaneous/export", height: 15, width: 15), // Image.asset("assets/miscellaneous/export", height: 15, width: 15),

View File

@ -8,7 +8,8 @@ class AppInputDecorations {
hintText: hint, hintText: hint,
filled: true, filled: true,
fillColor: const Color(0xFFECECEC), fillColor: const Color(0xFFECECEC),
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 15),
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 1),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Color(0xFFFFFFFF)), borderSide: const BorderSide(color: Color(0xFFFFFFFF)),
@ -16,7 +17,8 @@ class AppInputDecorations {
errorStyle: const TextStyle(color: Colors.red, fontSize: 12), errorStyle: const TextStyle(color: Colors.red, fontSize: 12),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Color(0xFFFFFFFF)),
borderSide: const BorderSide(color: Color(0xFFFFFFFF), width: 1),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),

View File

@ -6,9 +6,10 @@ import 'package:google_fonts/google_fonts.dart';
// import 'package:uae_stat/config/my_theme.dart'; // import 'package:uae_stat/config/my_theme.dart';
class ThemedFormField extends HookWidget { class ThemedFormField extends HookWidget {
const ThemedFormField({ ThemedFormField({
super.key, super.key,
this.hintText, this.hintText,
this.imgPath, this.imgPath,
this.txtwidth, this.txtwidth,
this.txtheight, this.txtheight,
@ -21,13 +22,22 @@ class ThemedFormField extends HookWidget {
this.errorBorderColor, this.errorBorderColor,
this.errorTextColor, this.errorTextColor,
this.readOnly = false, // new this.readOnly = false, // new
this.isdense = false, // new
this.keyboardType, // new this.keyboardType, // new
this.maxLength, // new this.maxLength, // new
this.inputFormatters, this.inputFormatters,
this.isShowCursor, this.isShowCursor,
this.onChanged,
this.verticalPad,
this.horizonalPad,
this.enableBorderWidth,
}); });
final double? verticalPad;
final double? horizonalPad;
final double? enableBorderWidth;
final String? hintText; final String? hintText;
final String? imgPath; final String? imgPath;
final String? Function(String? text)? validator; final String? Function(String? text)? validator;
final TextEditingController controller; final TextEditingController controller;
@ -39,11 +49,15 @@ class ThemedFormField extends HookWidget {
final Color? errorTextColor; final Color? errorTextColor;
final double? txtwidth; final double? txtwidth;
final bool readOnly; final bool readOnly;
final bool isdense;
final bool? isShowCursor; final bool? isShowCursor;
final TextInputType? keyboardType; final TextInputType? keyboardType;
final int? maxLength; final int? maxLength;
final double? txtheight; final double? txtheight;
final List<TextInputFormatter>? inputFormatters; final List<TextInputFormatter>? inputFormatters;
// void Function(dynamic value)? onChanged;
final ValueChanged<String>? onChanged;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -70,34 +84,49 @@ class ThemedFormField extends HookWidget {
), ),
); );
final inputDecoration = InputDecoration( final inputDecoration = InputDecoration(
// isDense: true, isDense: isdense,
errorStyle: TextStyle(color: const Color(0xFFD83731)), errorStyle: TextStyle(color: Color(0xFFD83731)),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: const Color(0xFFD83731), width: 1), borderSide: BorderSide(
color: errorBorderColor ?? Color(0xFFD83731),
width: 1,
),
), ),
focusedErrorBorder: OutlineInputBorder( focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: const Color(0xFFD83731), width: 1), borderSide: BorderSide(
color: errorBorderColor ?? Color(0xFFD83731),
width: 1,
),
), ),
filled: true, filled: true,
fillColor: backgroundColor ?? Colors.white, fillColor: backgroundColor ?? Colors.white,
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 15), contentPadding: EdgeInsets.symmetric(
vertical: verticalPad ?? 14,
horizontal: horizonalPad ?? 15,
),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide(color: Color(0xFF50A398)), // borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(color: Color(0xFFFFFFFF)), borderSide: BorderSide(
color: borderColor ?? Color(0xFFFFFFFF),
width: enableBorderWidth ?? 1,
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide(color: Color(0xFF50A398)), // borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(color: Color(0xFFFFFFFF)), borderSide: BorderSide(
color: borderColor ?? backgroundColor ?? Color(0xFFFFFFFF),
width: enableBorderWidth ?? 1,
),
), ),
focusedBorder: readOnly focusedBorder: readOnly
? OutlineInputBorder( ? OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide(color: Color(0xFF50A398)), // borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(color: Color(0xFFFFFFFF)), borderSide: BorderSide(color: borderColor ?? Color(0xFFFFFFFF)),
) )
: OutlineInputBorder( : OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -135,6 +164,7 @@ class ThemedFormField extends HookWidget {
); );
return Container( return Container(
width: txtwidth ?? MediaQuery.of(context).size.width, width: txtwidth ?? MediaQuery.of(context).size.width,
height: txtheight ?? null, height: txtheight ?? null,
child: DecoratedBox( child: DecoratedBox(
decoration: boxDecoration, decoration: boxDecoration,
@ -155,7 +185,7 @@ class ThemedFormField extends HookWidget {
minLines: (keyboardType == TextInputType.multiline) ? 3 : 1, minLines: (keyboardType == TextInputType.multiline) ? 3 : 1,
maxLines: (keyboardType == TextInputType.multiline) ? null : 1, maxLines: (keyboardType == TextInputType.multiline) ? null : 1,
style: GoogleFonts.inter( style: GoogleFonts.inter(
fontSize: 14, // 👈 Change this to your desired size fontSize: 12, // 👈 Change this to your desired size
fontWeight: FontWeight.w400, // optional fontWeight: FontWeight.w400, // optional
color: Colors.black, // optional color: Colors.black, // optional
), ),

View File

@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:google_fonts/google_fonts.dart';
// import 'package:uae_stat/config/my_theme.dart';
class ThemedFormInlineField extends HookWidget {
ThemedFormInlineField({
super.key,
this.hintText,
this.widthNone = false,
this.imgPath,
this.txtwidth,
this.txtheight,
required this.controller,
this.validator,
this.isObscurable = false,
this.hintColor,
this.backgroundColor,
this.borderColor,
this.errorBorderColor,
this.errorTextColor,
this.readOnly = false, // new
this.keyboardType, // new
this.maxLength, // new
this.inputFormatters,
this.isShowCursor,
this.onChanged,
this.isdense,
});
final String? hintText;
final bool? widthNone;
final String? imgPath;
final String? Function(String? text)? validator;
final TextEditingController controller;
final bool isObscurable;
final Color? hintColor;
final Color? backgroundColor;
final Color? borderColor;
final Color? errorBorderColor;
final Color? errorTextColor;
final double? txtwidth;
final bool readOnly;
final bool? isdense;
final bool? isShowCursor;
final TextInputType? keyboardType;
final int? maxLength;
final double? txtheight;
final List<TextInputFormatter>? inputFormatters;
// void Function(dynamic value)? onChanged;
final ValueChanged<String>? onChanged;
@override
Widget build(BuildContext context) {
final isObscured = useState<bool>(true);
final boxDecoration = BoxDecoration(
borderRadius: BorderRadius.circular(10),
// boxShadow: [
// BoxShadow(
// color: Colors.black26, // shadow color
// offset: Offset(0, 2), // x=0, y=4 shadow goes downward
// blurRadius: 2, // soften the shadow
// spreadRadius: 0.2, // expand shadow
// ),
// ],
);
final obscureBtn = IconButton(
onPressed: () => isObscured.value = !isObscured.value,
icon: Icon(
isObscured.value
? Icons.visibility_off_outlined
: Icons.visibility_outlined,
color: const Color(0xff9EA2A9),
),
);
final inputDecoration = InputDecoration(
isDense: isdense ?? false,
errorStyle: TextStyle(color: Color(0xFFD83731)),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: errorBorderColor ?? Color(0xFFD83731),
width: 1,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: errorBorderColor ?? Color(0xFFD83731),
width: 1,
),
),
filled: true,
fillColor: backgroundColor ?? Colors.white,
// contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 2),
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
// borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(
color: borderColor ?? Color(0xFFFFFFFF),
width: 0.1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
// borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(
color: borderColor ?? backgroundColor ?? Color(0xFFFFFFFF),
width: 0.1,
),
),
focusedBorder: readOnly
? OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
// borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(color: borderColor ?? Color(0xFFFFFFFF)),
)
: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(color: Color(0xFF50A398), width: 2),
),
hintText: hintText,
hintStyle: TextStyle(
// dynamic hint color
color: const Color(0xFFC3C6CB),
),
// labelText: hintText,
// labelStyle: GoogleFonts.inter(
// fontSize: 10,
// // color: Colors.black, // customize here
// color: Colors.red, // customize here
// fontWeight: FontWeight.w400,
// ),
prefixIconConstraints: const BoxConstraints(
maxWidth: 25 + 16 + 10,
maxHeight: 25 + (8 * 2),
),
prefixIcon: imgPath != null
? Padding(
padding: const EdgeInsetsDirectional.only(start: 16, end: 10),
child: Image.asset(
imgPath!,
fit: BoxFit.fitHeight,
height: 25,
width: 25,
),
)
: null,
suffixIcon: isObscurable ? obscureBtn : null,
);
return Container(
height: txtheight ?? null,
child: DecoratedBox(
decoration: boxDecoration,
child: TextFormField(
controller: controller,
decoration: inputDecoration,
validator: validator,
obscureText: isObscurable && isObscured.value,
readOnly: readOnly, // now supported
keyboardType: keyboardType, // e.g. TextInputType.number
maxLength: maxLength, // max length
showCursor: isShowCursor ?? true,
enableInteractiveSelection: isShowCursor ?? true,
inputFormatters:
inputFormatters ??
[FilteringTextInputFormatter.allow(RegExp(r'[a-z A-Z]'))],
// allow multiline if user sets maxLines / minLines
minLines: (keyboardType == TextInputType.multiline) ? 3 : 1,
maxLines: (keyboardType == TextInputType.multiline) ? null : 1,
style: GoogleFonts.inter(
fontSize: 12, // 👈 Change this to your desired size
fontWeight: FontWeight.w400, // optional
color: Colors.black, // optional
),
),
),
);
}
}
class UpperCaseTextFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
return TextEditingValue(
text: newValue.text.toUpperCase(),
selection: newValue.selection,
);
}
}

View File

@ -144,7 +144,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final spacing = 10.0; final spacing = 5.0;
// final rowChildren = [ // final rowChildren = [
// buildStartDate(context), // buildStartDate(context),
@ -186,7 +186,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
final buttons = [ final buttons = [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 0.0),
// child: GestureDetector( // child: GestureDetector(
// onTap: () { // onTap: () {
// if (formKey.currentState!.validate()) onFilter(); // if (formKey.currentState!.validate()) onFilter();
@ -206,7 +206,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(0.0),
// child: GestureDetector(onTap: onRefresh, child: Icon(Icons.refresh)), // child: GestureDetector(onTap: onRefresh, child: Icon(Icons.refresh)),
child: Tooltip( child: Tooltip(
message: 'Refresh', message: 'Refresh',
@ -235,11 +235,11 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
key: widget.formKey, key: widget.formKey,
child: widget.isMobile child: widget.isMobile
? Column( ? Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start,
children: getRowChildren(widget.isMobile, spacing), children: getRowChildren(widget.isMobile, spacing),
) )
: Row( : Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start,
children: getRowChildren(widget.isMobile, spacing), children: getRowChildren(widget.isMobile, spacing),
), ),
); );
@ -249,8 +249,8 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Start Date', style: _textStyle), // Text('Start Date', style: _textStyle),
SizedBox(height: 5), // SizedBox(height: 5),
ThemedDateField( ThemedDateField(
hintText: "Select Date", hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context) txtwidth: ResponsiveLayout.isMobile(context)
@ -274,8 +274,8 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('End Date', style: _textStyle), // Text('End Date', style: _textStyle),
SizedBox(height: 5), // SizedBox(height: 5),
ThemedDateField( ThemedDateField(
hintText: "Select Date", hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context) txtwidth: ResponsiveLayout.isMobile(context)
@ -336,8 +336,8 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Status', style: _textStyle), // Text('Status', style: _textStyle),
SizedBox(height: 5), // SizedBox(height: 5),
SizedBox( SizedBox(
height: 40, height: 40,
child: Container( child: Container(
@ -362,6 +362,18 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
compareFn: (item, selectedItem) => compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], item['id'] == selectedItem['id'],
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['status'].toString() : "",
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
// validator: (val) { // validator: (val) {
// if (val == null) { // if (val == null) {
// return "Please select a status"; // return "Please select a status";
@ -369,9 +381,19 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
// return null; // return null;
// }, // },
decoratorProps: DropDownDecoratorProps( decoratorProps: DropDownDecoratorProps(
decoration: AppInputDecorations.dropdownDecoration( decoration:
label: "Select Status", AppInputDecorations.dropdownDecoration(
).copyWith(filled: true, fillColor: Colors.white), label: "Select Status",
).copyWith(
filled: true,
fillColor: Colors.white,
isDense: true, // 👈 reduces built-in vertical padding
contentPadding: const EdgeInsets.symmetric(
horizontal: 2,
vertical: 1, // 👈 adjust this to make the field shorter
),
),
), ),
// popupProps: PopupProps.menu( // popupProps: PopupProps.menu(
// fit: FlexFit.loose, // fit: FlexFit.loose,
@ -455,104 +477,124 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text("Staff Name", style: _textStyle), // Text("Staff Name", style: _textStyle),
SizedBox(height: 10), // SizedBox(height: 10),
Container( SizedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(color: Colors.grey.shade100),
// color: Colors.black,
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.13,
height: 40, height: 40,
child: DropdownSearch<Map<String, dynamic>>( child: Container(
// key: dropDownKey, decoration: BoxDecoration(
key: ValueKey(selectedStaff), borderRadius: BorderRadius.circular(5.0),
// selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null, border: Border.all(color: Colors.grey.shade100),
selectedItem: selectedVehicle, // color: Colors.black,
items: (filter, infiniteScrollProps) {
return filteredStaffData;
},
itemAsString: (val) => val['name'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Staff ",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
),
), ),
popupProps: PopupProps.menu( width: ResponsiveLayout.isMobile(context)
fit: FlexFit.loose, ? null
constraints: BoxConstraints(maxHeight: 250), : MediaQuery.of(context).size.width * 0.13,
menuProps: MenuProps( // height: 45,
backgroundColor: child: DropdownSearch<Map<String, dynamic>>(
Colors.white, // 👈 sets dropdown background to white // key: dropDownKey,
), key: ValueKey(selectedStaff),
showSearchBox: true, // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
searchFieldProps: TextFieldProps( selectedItem: selectedVehicle,
decoration: InputDecoration(
filled: true, items: (filter, infiniteScrollProps) {
fillColor: Colors.white, return filteredStaffData;
hintText: "Search Staff ...", },
hintStyle: GoogleFonts.inter(
fontSize: 12, itemAsString: (val) => val['name'].toString(),
color: Colors.black, compareFn: (item, selectedItem) =>
), item['id'] == selectedItem['id'], // compare by id
enabledBorder: OutlineInputBorder( dropdownBuilder: (context, selectedItem) => Align(
borderSide: BorderSide( alignment: Alignment.centerLeft,
color: Colors.white, child: Text(
), // 👈 Normal border selectedItem != null ? selectedItem['name'].toString() : "",
), style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
focusedBorder: OutlineInputBorder( overflow: TextOverflow.ellipsis,
borderSide: BorderSide( maxLines: 1,
color: Colors.white, softWrap: false,
width: 1.5,
), // 👈 Focused border
),
), ),
), ),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Staff ",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
contentPadding: const EdgeInsets.symmetric(
horizontal: 2,
vertical: 1, // 👈 adjust this to make the field shorter
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) { popupProps: PopupProps.menu(
return Container( fit: FlexFit.loose,
// color: isSelected ? Colors.blue.withOpacity(0.1) : null, constraints: BoxConstraints(maxHeight: 250),
padding: const EdgeInsets.symmetric( menuProps: MenuProps(
horizontal: 8, backgroundColor:
vertical: 3, Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Staff ...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
), ),
child: Text( ),
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 13, color: Colors.black), itemBuilder: (context, item, isDisabled, isSelected) {
), return Container(
); // color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(
fontSize: 13,
color: Colors.black,
),
),
);
},
// constraints: BoxConstraints(),
),
onChanged: (val) {
if (val != null) {
print("Selected Staff : ${val['name']}");
print("Id: ${val['id']}");
selectedStaffName = val['name'];
selectedStaff = val['id'];
if (widget.onFilterStaff != null)
widget.onFilterStaff!(val['id']);
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
}, },
// constraints: BoxConstraints(),
), ),
onChanged: (val) {
if (val != null) {
print("Selected Staff : ${val['name']}");
print("Id: ${val['id']}");
selectedStaffName = val['name'];
selectedStaff = val['id'];
if (widget.onFilterStaff != null)
widget.onFilterStaff!(val['id']);
// controllers['agentId']?.text = val['agent_code'];
// agentId = agent['id'];
}
},
), ),
), ),
], ],

View File

@ -65,6 +65,10 @@ class _TopBarState extends ConsumerState<TopBar> {
titleSpacing: widget.isMobile ? 0 : 30, titleSpacing: widget.isMobile ? 0 : 30,
title: Row( title: Row(
children: [ children: [
IconButton(
icon: const Icon(Icons.menu, color: Colors.black87),
onPressed: widget.onMenuPressed,
),
if (!widget.isMobile) if (!widget.isMobile)
Image.asset( Image.asset(
"assets/login/nhance-partner-logo.png", "assets/login/nhance-partner-logo.png",
@ -103,11 +107,7 @@ class _TopBarState extends ConsumerState<TopBar> {
Spacer(), Spacer(),
// else if (onMenuPressed != null) // if (onMenuPressed != null)
// IconButton(
// icon: const Icon(Icons.menu, color: Colors.black87),
// onPressed: onMenuPressed,
// ),
if (role == 'agent') ...[ if (role == 'agent') ...[
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {

View File

@ -70,6 +70,8 @@ dependencies:
file_picker: ^10.3.3 file_picker: ^10.3.3
fluttertoast: ^9.0.0 fluttertoast: ^9.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter