staff enq modification
This commit is contained in:
parent
857179ca65
commit
7b0e14c1ba
File diff suppressed because one or more lines are too long
@ -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,
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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 *",
|
||||
|
||||
@ -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
1375
lib/presentation/screens/handler/enquiryListOld.dart
Normal file
1375
lib/presentation/screens/handler/enquiryListOld.dart
Normal file
File diff suppressed because it is too large
Load Diff
155
lib/presentation/screens/handler/inline
Normal file
155
lib/presentation/screens/handler/inline
Normal 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(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
515
lib/presentation/screens/handler/new
Normal file
515
lib/presentation/screens/handler/new
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
582
lib/presentation/screens/handler/quotationPopUp.dart
Normal file
582
lib/presentation/screens/handler/quotationPopUp.dart
Normal 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')),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
2904
lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart
Normal file
2904
lib/presentation/screens/staff/Enquiry/enquiry_inline_list.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
),
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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'];
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -70,6 +70,8 @@ dependencies:
|
||||
file_picker: ^10.3.3
|
||||
fluttertoast: ^9.0.0
|
||||
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Loading…
Reference in New Issue
Block a user