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/UserManagement/Profile/profile_web.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 '../../presentation/providers/manager_provider.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/Staff/staff.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/login/login_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/policy/policy.dart';
import '../../presentation/screens/staff/policy/policy_list.dart';
@ -157,11 +160,13 @@ final GoRouter appRouter = GoRouter(
GoRoute(
path: AppRoutes.enquiryForStaff,
builder: (context, state) => const EnquiryStaff(),
builder: (context, state) => const EnquiryListStaffInline(),
// builder: (context, state) => const EnquiryStaff(),
),
GoRoute(
path: AppRoutes.enquiryHandlerLst,
builder: (context, state) => const EnquiryHandler(),
// builder: (context, state) => const EnquiryHandler(),
builder: (context, state) => EnquiryListHandler(),
),
GoRoute(
path: AppRoutes.allStaffAttendance,

View File

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

View File

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

View File

@ -33,7 +33,7 @@ class MainLayout extends ConsumerStatefulWidget {
class MainLayoutState extends ConsumerState<MainLayout> {
dynamic role;
ApiService apiService = ApiService();
bool isDrawerOpen = true;
@override
void initState() {
super.initState();
@ -93,7 +93,12 @@ class MainLayoutState extends ConsumerState<MainLayout> {
appBar: TopBar(
title: widget.title,
onMenuPressed: () {
Scaffold.of(context).openDrawer();
// Scaffold.of(context).openDrawer();
// Scaffold.of(context).openDrawer();
setState(() {
isDrawerOpen = !isDrawerOpen;
});
},
onLogout: () async {
debugPrint("Logout tapped");
@ -118,7 +123,7 @@ class MainLayoutState extends ConsumerState<MainLayout> {
items: [
PopupMenuItem(
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(
children: [
const SizedBox(
width: 100, // fixed width for drawer
child: DrawerMenu(),
AnimatedContainer(
duration: const Duration(milliseconds: 250),
width: isDrawerOpen ? 100 : 0, // 👈 adjust width
child: isDrawerOpen
? const DrawerMenu()
: const SizedBox.shrink(), // hides drawer
),
Expanded(
child: Container(
padding: EdgeInsets.all(2.0),
padding: const EdgeInsets.all(8.0),
color: Colors.white,
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 enquiryIdProvider = StateProvider<String?>((ref) => null);
final navFromEnqStaffProvider = StateProvider<String?>((ref) => null);
final enqBrokerNameProvider = StateProvider<String?>((ref) => null);
final staffIndiviualAttendanceIdProvider = StateProvider<String?>(
(ref) => null,
);

View File

@ -86,12 +86,12 @@ class EnquiryTabState extends ConsumerState<EnquiryTab> {
String? selectedInsurer;
String? selectedAgent;
Map<String, TextEditingController> controllers = {};
String? _token;
dynamic userId;
dynamic managerId;
dynamic role;
Map<String, TextEditingController> controllers = {};
List<Map<String, dynamic>> getVehicleTypeData = [];
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) {
return buildResponsiveField(
label: "Insured Name *",

View File

@ -677,7 +677,8 @@ class StaffState extends ConsumerState<Staff> {
(item) => item['id'].toString() == selectedHandler,
orElse: () => {},
);
final isReadOnly = widget.id != null;
// final isReadOnly = widget.id != null;
final isReadOnly = false;
return Row(
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'];
// setState(() {
// enteredEmailOrMobile = true;
// });
setState(() {
enteredEmailOrMobile = true;
});
if (verification == true) {
// 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,
child: InkWell(
hoverColor: Color(0xFFEAF6F4),
// hoverColor: const Color(0xffD9EBE8),
// focusColor: const Color(0xffD9EBE8),
// splashColor: const Color(0xffD9EBE8),
// highlightColor: const Color(0xffD9EBE8),
onTap: () async {
// Get overlay and button position RELATIVE to InkWell
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) {
print('updateEnquiryData - $enquiryData');
if (!mounted) return;
setState(() {
selectedInsurdId = enquiryData['insurer_id'];
selectedEnquiryId = enquiryData['id'];
@ -192,13 +193,14 @@ class QuotationStaffTabState extends ConsumerState<QuotationStaffTab> {
Future<void> getQuotationList(enqQuotation) async {
print('getClaimList called enqQuotation- $enqQuotation');
if (!mounted) return;
setState(() {
isLoading = true;
});
try {
final response = await apiService.findEnqQuotePolicyView(enqQuotation);
if (!mounted) return;
if (response['status'] == 'success') {
print('quoationListData - ${response['data']}');
setState(() {
@ -226,6 +228,7 @@ class QuotationStaffTabState extends ConsumerState<QuotationStaffTab> {
} catch (e) {
print('Exception occurred: $e');
} finally {
if (!mounted) return;
setState(() {
isLoading = false;
});
@ -266,7 +269,8 @@ class QuotationStaffTabState extends ConsumerState<QuotationStaffTab> {
// scrollDirection: Axis.vertical,
child: Column(
children: [
if (!blockKey && roleId != 'manager') ...[
if (!blockKey) ...[
// if (!blockKey && roleId != 'manager') ...[
CreateQuotationForm(
key: ValueKey(selectedQuotationFrmListId ?? "new"),
userId: userId,

View File

@ -39,6 +39,7 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
dynamic selectedInsuredName;
dynamic selectedVehicleNum;
dynamic selectedVehicleType;
dynamic selectedBrokerName;
@override
void initState() {
@ -97,8 +98,9 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
selectedInsuredName = data['enquiry']?['name'];
selectedVehicleNum = data['enquiry']?['reg_no'];
selectedVehicleType = data['enquiry']?['vehicle_type'];
selectedBrokerName = data['enquiry']?['broker_name'];
print(
'loadQuotationTab 4s - $selectedInsuredName - $selectedVehicleNum - $selectedVehicleType',
'loadQuotationTab 4s -$selectedBrokerName $selectedInsuredName - $selectedVehicleNum - $selectedVehicleType',
);
tabs = [
@ -126,6 +128,24 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
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
@ -146,9 +166,8 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
return AlertDialog(
backgroundColor: Colors.white,
content: Container(
width: MediaQuery.of(context).size.width * 0.6,
height: MediaQuery.of(context).size.height * 0.8,
width: MediaQuery.of(context).size.width * 0.65,
height: MediaQuery.of(context).size.height * 0.9,
child: isLoading
? const Center(child: CircularProgressIndicator())
: tabs.isEmpty
@ -211,6 +230,7 @@ class TabEnquiryStaffListState extends ConsumerState<TabEnquiryStaffList> {
insured_name(context),
vehcile_num(context),
vehcile_Type(context),
broker(context),
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(
fontSize: 12,
fontWeight: FontWeight.w500,

View File

@ -51,19 +51,26 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
List<Map<String, dynamic>> getInsurersData = [];
List<Map<String, dynamic>> filteredInsurersData = [];
String? selectedInsurer;
List<Map<String, dynamic>> getBrokerData = [];
List<Map<String, dynamic>> filteredBrokerData = [];
String? selectedBroker;
final _formKey = GlobalKey<FormState>();
final _formKeyEndrosment = GlobalKey<FormState>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownKeyInsurer = GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
List<String> tabHeader = ['regNum'];
List<Map<String, dynamic>> getStaffDetailsData = [];
List<Map<String, dynamic>> filteredStaffData = [];
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
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? selectedRegNum;
@ -74,6 +81,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
"id": widget.enquiryPrimaryId,
"assigned_to": selectedStaff,
"insurer_id": selectedInsurer,
"broker_id": selectedBroker,
"updated_by": widget.userId,
};
return data;
@ -92,17 +100,19 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
// 🔹 Init logic here (API calls, token fetch, etc.)
_initializeToken();
// getStaffDetails(1);
// getStaffDetailsForEnquiryAssignment(1);
getInsurers();
getBroker();
Future.microtask(() {
final managerId = ref.watch(managerIdProvider);
final userID = ref.watch(userIdProvider);
// final handlerId = ref.watch(handlerIdProvider);
role = ref.watch(userRoleProvider);
final userID = ref.watch(userIdProvider);
print("managerId - $managerId");
if (userID != null) {
print('hansles');
getStaffDetails(userID);
getStaffDetailsForEnquiryAssignment(userID);
}
});
}
@ -143,8 +153,39 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
}
}
Future<void> getStaffDetails(int id) async {
print('getStaffDetails called By handler');
Future<void> getBroker() async {
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(() {
isLoading = true;
});
@ -157,19 +198,19 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
);
if (response['status'] == 'success') {
print('getStaffDetails - ${response['data']}');
print('getStaffDetailsForEnquiryAssignment - ${response['data']}');
setState(() {
getStaffDetailsData = List<Map<String, dynamic>>.from(
getStaffDetailsDataEnqAsgn = List<Map<String, dynamic>>.from(
response['data'],
);
print('API Data - $getStaffDetailsData');
print('API Data - $getStaffDetailsDataEnqAsgn');
filteredStaffData = List.from(getStaffDetailsData);
print('originalData - $filteredStaffData');
filteredStaffDataEnqAsgn = List.from(getStaffDetailsDataEnqAsgn);
print('originalData - $filteredStaffDataEnqAsgn');
});
} else {
getStaffDetailsData = [];
filteredStaffData = [];
getStaffDetailsDataEnqAsgn = [];
filteredStaffDataEnqAsgn = [];
}
} catch (e) {
print('Exception occurred: $e');
@ -363,6 +404,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
buildRegistrationNumber(context),
buildSelectStaffMem(context),
buildInsurer(context),
buildBroker(context),
],
),
);
@ -409,14 +451,14 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
}
Widget buildSelectStaffMem(ctx) {
// Map<String, dynamic>? selectedVehicle = filteredStaffData.firstWhere(
// Map<String, dynamic>? selectedVehicle = filteredStaffDataEnqAsgn.firstWhere(
// (item) => item['id'].toString() == selectedStaff,
// orElse: () => {},
// );
Map<String, dynamic>? selectedVehicle;
try {
selectedVehicle = filteredStaffData.firstWhere(
selectedVehicle = filteredStaffDataEnqAsgn.firstWhere(
(item) => item['id'].toString() == selectedStaff,
);
} catch (e) {
@ -439,7 +481,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
// selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
selectedItem: selectedVehicle,
items: (filter, infiniteScrollProps) {
return filteredStaffData;
return filteredStaffDataEnqAsgn;
},
itemAsString: (val) => val['name'].toString(),
@ -490,6 +532,19 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
),
),
// 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) {
@ -528,7 +583,7 @@ class _AddDialogState extends ConsumerState<AssignStaffDialog> {
: MediaQuery.of(context).size.width * 0.26,
// height: 40,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyInsurer,
key: dropDownKeyInsurerEnqAsgn,
selectedItem: selectedInsurerd.isNotEmpty ? selectedInsurerd : null,
items: (filter, infiniteScrollProps) {
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(),
),
@ -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 ---------------------------------
static final TextStyle _textStyle = TextStyle(

View File

@ -1,8 +1,157 @@
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import 'package:google_fonts/google_fonts.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> {
// ThemedUploadField({
// 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(
mainAxisSize: MainAxisSize.min,
children: [
?txt!
? Text(
'Export',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 14,
),
)
: null,
?txt! ? SizedBox(width: 15) : null,
// ?txt!
// ? Text(
// 'Export',
// style: TextStyle(
// color: Colors.white,
// fontWeight: FontWeight.w600,
// fontSize: 14,
// ),
// )
// : null,
// ?txt! ? SizedBox(width: 15) : null,
Image.asset(
"assets/miscellaneous/export.png",
height: 25,
width: 25,
height: 15,
width: 15,
),
// Icon(Icons.input_sharp, color: Colors.white),
// Image.asset("assets/miscellaneous/export", height: 15, width: 15),

View File

@ -8,7 +8,8 @@ class AppInputDecorations {
hintText: hint,
filled: true,
fillColor: const Color(0xFFECECEC),
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 15),
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 1),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Color(0xFFFFFFFF)),
@ -16,7 +17,8 @@ class AppInputDecorations {
errorStyle: const TextStyle(color: Colors.red, fontSize: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: const BorderSide(color: Color(0xFFFFFFFF)),
borderSide: const BorderSide(color: Color(0xFFFFFFFF), width: 1),
),
focusedBorder: OutlineInputBorder(
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';
class ThemedFormField extends HookWidget {
const ThemedFormField({
ThemedFormField({
super.key,
this.hintText,
this.imgPath,
this.txtwidth,
this.txtheight,
@ -21,13 +22,22 @@ class ThemedFormField extends HookWidget {
this.errorBorderColor,
this.errorTextColor,
this.readOnly = false, // new
this.isdense = false, // new
this.keyboardType, // new
this.maxLength, // new
this.inputFormatters,
this.isShowCursor,
this.onChanged,
this.verticalPad,
this.horizonalPad,
this.enableBorderWidth,
});
final double? verticalPad;
final double? horizonalPad;
final double? enableBorderWidth;
final String? hintText;
final String? imgPath;
final String? Function(String? text)? validator;
final TextEditingController controller;
@ -39,11 +49,15 @@ class ThemedFormField extends HookWidget {
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) {
@ -70,34 +84,49 @@ class ThemedFormField extends HookWidget {
),
);
final inputDecoration = InputDecoration(
// isDense: true,
errorStyle: TextStyle(color: const Color(0xFFD83731)),
isDense: isdense,
errorStyle: TextStyle(color: Color(0xFFD83731)),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: const Color(0xFFD83731), width: 1),
borderSide: BorderSide(
color: errorBorderColor ?? Color(0xFFD83731),
width: 1,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: const Color(0xFFD83731), width: 1),
borderSide: BorderSide(
color: errorBorderColor ?? Color(0xFFD83731),
width: 1,
),
),
filled: true,
fillColor: backgroundColor ?? Colors.white,
contentPadding: const EdgeInsets.symmetric(vertical: 14, horizontal: 15),
contentPadding: EdgeInsets.symmetric(
vertical: verticalPad ?? 14,
horizontal: horizonalPad ?? 15,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(color: Color(0xFFFFFFFF)),
borderSide: BorderSide(
color: borderColor ?? Color(0xFFFFFFFF),
width: enableBorderWidth ?? 1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(color: Color(0xFFFFFFFF)),
borderSide: BorderSide(
color: borderColor ?? backgroundColor ?? Color(0xFFFFFFFF),
width: enableBorderWidth ?? 1,
),
),
focusedBorder: readOnly
? OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
// borderSide: BorderSide(color: Color(0xFF50A398)),
borderSide: BorderSide(color: Color(0xFFFFFFFF)),
borderSide: BorderSide(color: borderColor ?? Color(0xFFFFFFFF)),
)
: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
@ -135,6 +164,7 @@ class ThemedFormField extends HookWidget {
);
return Container(
width: txtwidth ?? MediaQuery.of(context).size.width,
height: txtheight ?? null,
child: DecoratedBox(
decoration: boxDecoration,
@ -155,7 +185,7 @@ class ThemedFormField extends HookWidget {
minLines: (keyboardType == TextInputType.multiline) ? 3 : 1,
maxLines: (keyboardType == TextInputType.multiline) ? null : 1,
style: GoogleFonts.inter(
fontSize: 14, // 👈 Change this to your desired size
fontSize: 12, // 👈 Change this to your desired size
fontWeight: FontWeight.w400, // 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
Widget build(BuildContext context) {
final spacing = 10.0;
final spacing = 5.0;
// final rowChildren = [
// buildStartDate(context),
@ -186,7 +186,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
final buttons = [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
padding: const EdgeInsets.symmetric(vertical: 0.0),
// child: GestureDetector(
// onTap: () {
// if (formKey.currentState!.validate()) onFilter();
@ -206,7 +206,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
),
),
Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(0.0),
// child: GestureDetector(onTap: onRefresh, child: Icon(Icons.refresh)),
child: Tooltip(
message: 'Refresh',
@ -235,11 +235,11 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
key: widget.formKey,
child: widget.isMobile
? Column(
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: getRowChildren(widget.isMobile, spacing),
)
: Row(
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: getRowChildren(widget.isMobile, spacing),
),
);
@ -249,8 +249,8 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Start Date', style: _textStyle),
SizedBox(height: 5),
// Text('Start Date', style: _textStyle),
// SizedBox(height: 5),
ThemedDateField(
hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context)
@ -274,8 +274,8 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('End Date', style: _textStyle),
SizedBox(height: 5),
// Text('End Date', style: _textStyle),
// SizedBox(height: 5),
ThemedDateField(
hintText: "Select Date",
txtwidth: ResponsiveLayout.isMobile(context)
@ -336,8 +336,8 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Status', style: _textStyle),
SizedBox(height: 5),
// Text('Status', style: _textStyle),
// SizedBox(height: 5),
SizedBox(
height: 40,
child: Container(
@ -362,6 +362,18 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
compareFn: (item, selectedItem) =>
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) {
// if (val == null) {
// return "Please select a status";
@ -369,9 +381,19 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
// return null;
// },
decoratorProps: DropDownDecoratorProps(
decoration: AppInputDecorations.dropdownDecoration(
label: "Select Status",
).copyWith(filled: true, fillColor: Colors.white),
decoration:
AppInputDecorations.dropdownDecoration(
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(
// fit: FlexFit.loose,
@ -455,104 +477,124 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Staff Name", style: _textStyle),
SizedBox(height: 10),
Container(
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,
// Text("Staff Name", style: _textStyle),
// SizedBox(height: 10),
SizedBox(
height: 40,
child: DropdownSearch<Map<String, dynamic>>(
// key: dropDownKey,
key: ValueKey(selectedStaff),
// 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
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Staff ",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(color: Colors.grey.shade100),
// color: Colors.black,
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Staff ...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
width: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.13,
// height: 45,
child: DropdownSearch<Map<String, dynamic>>(
// key: dropDownKey,
key: ValueKey(selectedStaff),
// selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
selectedItem: selectedVehicle,
items: (filter, infiniteScrollProps) {
return filteredStaffData;
},
itemAsString: (val) => val['name'].toString(),
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // compare by id
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['name'].toString() : "",
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Select Staff ",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
contentPadding: const EdgeInsets.symmetric(
horizontal: 2,
vertical: 1, // 👈 adjust this to make the field shorter
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Staff ...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
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,
title: Row(
children: [
IconButton(
icon: const Icon(Icons.menu, color: Colors.black87),
onPressed: widget.onMenuPressed,
),
if (!widget.isMobile)
Image.asset(
"assets/login/nhance-partner-logo.png",
@ -103,11 +107,7 @@ class _TopBarState extends ConsumerState<TopBar> {
Spacer(),
// else if (onMenuPressed != null)
// IconButton(
// icon: const Icon(Icons.menu, color: Colors.black87),
// onPressed: onMenuPressed,
// ),
// if (onMenuPressed != null)
if (role == 'agent') ...[
GestureDetector(
onTap: () async {

View File

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