insurer master ans claims
This commit is contained in:
parent
78d4d786a7
commit
6c65a71d1f
File diff suppressed because one or more lines are too long
@ -5,8 +5,8 @@ class Env {
|
|||||||
);
|
);
|
||||||
static const String apiUrl = String.fromEnvironment(
|
static const String apiUrl = String.fromEnvironment(
|
||||||
'API_URL',
|
'API_URL',
|
||||||
// defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */
|
defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */
|
||||||
defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */
|
// defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */
|
||||||
// defaultValue: 'http://localhost/nhance_partner_be/', /* localhost build (enable index.html line 19) */
|
// defaultValue: 'http://localhost/nhance_partner_be/', /* localhost build (enable index.html line 19) */
|
||||||
);
|
);
|
||||||
// static const String baseUrl = String.fromEnvironment(
|
// static const String baseUrl = String.fromEnvironment(
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import '../../presentation/screens/Enquiry/policy_claims_endros/endorsomentUpdat
|
|||||||
import '../../presentation/screens/Enquiry/policy_claims_endros/endrosment.dart';
|
import '../../presentation/screens/Enquiry/policy_claims_endros/endrosment.dart';
|
||||||
import '../../presentation/screens/Masters/Brokers/brokerList.dart';
|
import '../../presentation/screens/Masters/Brokers/brokerList.dart';
|
||||||
import '../../presentation/screens/Masters/EndorsementType/endorsementList.dart';
|
import '../../presentation/screens/Masters/EndorsementType/endorsementList.dart';
|
||||||
|
import '../../presentation/screens/Masters/Insurers/insurerList.dart';
|
||||||
import '../../presentation/screens/Masters/VehicleType/vehicleList.dart';
|
import '../../presentation/screens/Masters/VehicleType/vehicleList.dart';
|
||||||
import '../../presentation/screens/StaffAttendance/attendanceAllDetails.dart';
|
import '../../presentation/screens/StaffAttendance/attendanceAllDetails.dart';
|
||||||
import '../../presentation/screens/StaffAttendance/individual_Attendance.dart';
|
import '../../presentation/screens/StaffAttendance/individual_Attendance.dart';
|
||||||
@ -381,6 +382,10 @@ final GoRouter appRouter = GoRouter(
|
|||||||
path: AppRoutes.paymentModeLst,
|
path: AppRoutes.paymentModeLst,
|
||||||
builder: (context, state) => const PaymentLsit(),
|
builder: (context, state) => const PaymentLsit(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: AppRoutes.insurerLst,
|
||||||
|
builder: (context, state) => const InsurerList(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: AppRoutes.gridList,
|
path: AppRoutes.gridList,
|
||||||
builder: (context, state) => const GridListScreen(),
|
builder: (context, state) => const GridListScreen(),
|
||||||
|
|||||||
@ -55,6 +55,7 @@ class AppRoutes {
|
|||||||
static const String paymentModeLst = '/paymentMode';
|
static const String paymentModeLst = '/paymentMode';
|
||||||
static const String endorsementTypeLst = '/endorsementTypeLst';
|
static const String endorsementTypeLst = '/endorsementTypeLst';
|
||||||
static const String vehicleTypeLst = '/vehicleTypeLst';
|
static const String vehicleTypeLst = '/vehicleTypeLst';
|
||||||
|
static const String insurerLst = '/insurerLst';
|
||||||
static const String payoutList = '/payoutList';
|
static const String payoutList = '/payoutList';
|
||||||
static const String payoutDetails = '/payoutDetails';
|
static const String payoutDetails = '/payoutDetails';
|
||||||
static const String payoutDetailsView = '/payoutDetailsView';
|
static const String payoutDetailsView = '/payoutDetailsView';
|
||||||
|
|||||||
@ -734,7 +734,10 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
else if (masterName == 'VehicleType') {
|
else if (masterName == 'VehicleType') {
|
||||||
url = Uri.parse('${Env.apiUrl}master/updateVehicleTypeStatus/$id');
|
url = Uri.parse('${Env.apiUrl}master/updateVehicleTypeStatus/$id');
|
||||||
}
|
}
|
||||||
|
else if (masterName == 'Insurer') {
|
||||||
|
url = Uri.parse('${Env.apiUrl}master/updateInsurerStatus/$id');
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
url = Uri.parse('${Env.apiUrl}master/updatePaymentModeStatus/$id');
|
url = Uri.parse('${Env.apiUrl}master/updatePaymentModeStatus/$id');
|
||||||
}
|
}
|
||||||
@ -2233,18 +2236,28 @@ class ApiService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> fetchAgentUnusedCommissionList(id) async {
|
Future<Map<String, dynamic>> fetchAgentUnusedCommissionList(
|
||||||
|
id, {
|
||||||
|
String? fromDate,
|
||||||
|
String? toDate,
|
||||||
|
}) async {
|
||||||
print('fetchAGENTNameDropDown');
|
print('fetchAGENTNameDropDown');
|
||||||
if (_token == null) {
|
if (_token == null) {
|
||||||
await _initializeToken();
|
await _initializeToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final queryParameters = <String, String>{
|
||||||
|
'manager_id': id.toString(),
|
||||||
|
if (fromDate != null && fromDate.trim().isNotEmpty)
|
||||||
|
'from_date': fromDate.trim(),
|
||||||
|
if (toDate != null && toDate.trim().isNotEmpty) 'to_date': toDate.trim(),
|
||||||
|
};
|
||||||
|
|
||||||
dynamic url;
|
dynamic url;
|
||||||
print('fetchAGENTNameDropDown 1');
|
print('fetchAGENTNameDropDown 1');
|
||||||
url = Uri.parse(
|
url = Uri.parse(
|
||||||
'${Env.apiUrl}invoice/getAgentUnusedCommissionList?manager_id=$id',
|
'${Env.apiUrl}invoice/getAgentUnusedCommissionList',
|
||||||
);
|
).replace(queryParameters: queryParameters);
|
||||||
|
|
||||||
|
|
||||||
print('fetchAGENTNameDropDown 2');
|
print('fetchAGENTNameDropDown 2');
|
||||||
final headers = {
|
final headers = {
|
||||||
@ -2438,24 +2451,33 @@ class ApiService {
|
|||||||
Future<Map<String, dynamic>> getPayoutList({
|
Future<Map<String, dynamic>> getPayoutList({
|
||||||
String? fromDate,
|
String? fromDate,
|
||||||
String? toDate,
|
String? toDate,
|
||||||
|
String? agentName,
|
||||||
|
String? agentCode,
|
||||||
|
String? policyNo,
|
||||||
}) async {
|
}) async {
|
||||||
// print(_token);
|
// print(_token);
|
||||||
if (_token == null) {
|
if (_token == null) {
|
||||||
await _initializeToken();
|
await _initializeToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
final Uri url;
|
final query = <String, String>{};
|
||||||
if (fromDate != null &&
|
void addParam(String key, String? value) {
|
||||||
fromDate.isNotEmpty &&
|
final trimmed = value?.trim();
|
||||||
toDate != null &&
|
if (trimmed != null && trimmed.isNotEmpty) {
|
||||||
toDate.isNotEmpty) {
|
query[key] = trimmed;
|
||||||
url = Uri.parse('${Env.apiUrl}invoice/list').replace(
|
}
|
||||||
queryParameters: {'from_date': fromDate, 'to_date': toDate},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
url = Uri.parse('${Env.apiUrl}invoice/list');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addParam('from_date', fromDate);
|
||||||
|
addParam('to_date', toDate);
|
||||||
|
addParam('agent_name', agentName);
|
||||||
|
addParam('agent_code', agentCode);
|
||||||
|
addParam('policy_no', policyNo);
|
||||||
|
|
||||||
|
final url = Uri.parse('${Env.apiUrl}invoice/list').replace(
|
||||||
|
queryParameters: query.isEmpty ? null : query,
|
||||||
|
);
|
||||||
|
|
||||||
final headers = {
|
final headers = {
|
||||||
'Authorization': 'Bearer $_token' ?? '',
|
'Authorization': 'Bearer $_token' ?? '',
|
||||||
'app-signature': Env.App_Signature,
|
'app-signature': Env.App_Signature,
|
||||||
|
|||||||
@ -99,7 +99,8 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
|||||||
if (route.contains('broker') ||
|
if (route.contains('broker') ||
|
||||||
route.contains('payment') ||
|
route.contains('payment') ||
|
||||||
route.contains('vehicletype') ||
|
route.contains('vehicletype') ||
|
||||||
route.contains('endorsementtype')) {
|
route.contains('endorsementtype') ||
|
||||||
|
route.contains('insurer')) {
|
||||||
return 'Masters';
|
return 'Masters';
|
||||||
}
|
}
|
||||||
if (route.contains('payoutgrid')) {
|
if (route.contains('payoutgrid')) {
|
||||||
@ -361,6 +362,15 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
|||||||
context.go(AppRoutes.paymentModeLst);
|
context.go(AppRoutes.paymentModeLst);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 2),
|
||||||
|
_buildPopupItem(
|
||||||
|
label: "Insurer",
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _activeMenu = 'Masters');
|
||||||
|
_hidePopup();
|
||||||
|
context.go(AppRoutes.insurerLst);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
if (key == 'Payout' && role == 'Accounts') ...[
|
if (key == 'Payout' && role == 'Accounts') ...[
|
||||||
|
|||||||
277
lib/presentation/screens/Masters/Insurers/insurer.dart
Normal file
277
lib/presentation/screens/Masters/Insurers/insurer.dart
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:nhance_partner/data/utils/toastNotification.dart';
|
||||||
|
import 'package:nhance_partner/presentation/themes/indicators/text_field_theme_inline_editor.dart';
|
||||||
|
|
||||||
|
import '../../../../core/config/env.dart';
|
||||||
|
import '../../../../core/services/api_service.dart';
|
||||||
|
import '../../../../data/services/auth_service.dart';
|
||||||
|
import '../../../../data/utils/validators.dart';
|
||||||
|
|
||||||
|
class Insurer extends ConsumerStatefulWidget {
|
||||||
|
final String? id;
|
||||||
|
final Map<String, dynamic>? data;
|
||||||
|
final VoidCallback onSubmit;
|
||||||
|
|
||||||
|
const Insurer({
|
||||||
|
super.key,
|
||||||
|
this.id,
|
||||||
|
this.data,
|
||||||
|
required this.onSubmit,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<Insurer> createState() => InsurerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsurerState extends ConsumerState<Insurer> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late ApiService apiService;
|
||||||
|
|
||||||
|
List<String> tabHeader = ['name', 'code'];
|
||||||
|
String? _token;
|
||||||
|
dynamic selectedId;
|
||||||
|
|
||||||
|
Map<String, TextEditingController> controllers = {};
|
||||||
|
|
||||||
|
bool get _isEdit => selectedId != null;
|
||||||
|
|
||||||
|
Map<String, dynamic> dataDetails() {
|
||||||
|
final data = <String, dynamic>{
|
||||||
|
'name': controllers['name']?.text.trim(),
|
||||||
|
'short_name': controllers['code']?.text.trim(),
|
||||||
|
};
|
||||||
|
if (_isEdit) {
|
||||||
|
data['id'] = selectedId.toString();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
apiService = ApiService();
|
||||||
|
for (final field in tabHeader) {
|
||||||
|
controllers[field] = TextEditingController();
|
||||||
|
}
|
||||||
|
_initializeToken();
|
||||||
|
updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateData() {
|
||||||
|
if (widget.data == null) return;
|
||||||
|
|
||||||
|
final data = widget.data!;
|
||||||
|
selectedId = data['id'];
|
||||||
|
controllers['name']?.text = data['name']?.toString() ?? '';
|
||||||
|
controllers['code']?.text = data['short_name']?.toString() ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initializeToken() async {
|
||||||
|
_token = await AuthService.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleSave() async {
|
||||||
|
final nameEmpty =
|
||||||
|
controllers['name'] == null || controllers['name']!.text.trim().isEmpty;
|
||||||
|
final codeEmpty =
|
||||||
|
controllers['code'] == null || controllers['code']!.text.trim().isEmpty;
|
||||||
|
|
||||||
|
if (nameEmpty && codeEmpty) {
|
||||||
|
ToastHelper.showWarningToast(
|
||||||
|
context,
|
||||||
|
'Insurer and Short Name are required',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nameEmpty) {
|
||||||
|
ToastHelper.showWarningToast(context, 'Insurer is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (codeEmpty) {
|
||||||
|
ToastHelper.showWarningToast(context, 'Short Name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_formKey.currentState!.validate()) return;
|
||||||
|
await createUserData(dataDetails());
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
widget.onSubmit();
|
||||||
|
setState(() {
|
||||||
|
selectedId = null;
|
||||||
|
for (final controller in controllers.values) {
|
||||||
|
controller.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createUserData(Map<String, dynamic> data) async {
|
||||||
|
if (_token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.post(
|
||||||
|
Uri.parse('${Env.apiUrl}master/saveInsurer'),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $_token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': Env.App_Signature,
|
||||||
|
},
|
||||||
|
body: jsonEncode(data),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseBody = jsonDecode(response.body);
|
||||||
|
final message = responseBody['message']?.toString() ??
|
||||||
|
(_isEdit
|
||||||
|
? 'Insurer updated successfully.'
|
||||||
|
: 'Insurer created successfully.');
|
||||||
|
|
||||||
|
if (responseBody['status'] == 200 ||
|
||||||
|
responseBody['status'] == 'success') {
|
||||||
|
refresh();
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showSuccessToast(context, message);
|
||||||
|
}
|
||||||
|
} else if (mounted) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(_isEdit ? 'Insurer Update Failed' : 'Insurer Save Failed'),
|
||||||
|
content: Text(message),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: const Text('OK'),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
await apiService.clearLocalStorageAndRedirect();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showWarningToast(context, 'Error saving insurer: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (final controller in controllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SelectionArea(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
buildFormFields(),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
InkWell(
|
||||||
|
onTap: handleSave,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
color: const Color(0xFF2E7D6E),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Save',
|
||||||
|
style: GoogleFonts.inter(color: Colors.white, fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
InkWell(
|
||||||
|
onTap: refresh,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(6.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF2E7D6E),
|
||||||
|
borderRadius: BorderRadius.circular(5.0),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.refresh, size: 13, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildFormFields() {
|
||||||
|
return Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
buildName(),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
buildShortName(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildName() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Insurer *', style: _textStyle),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
ThemedFormInlineField(
|
||||||
|
controller: controllers['name']!,
|
||||||
|
validator: (value) => Validators.requiredField(value, 'name'),
|
||||||
|
txtwidth: MediaQuery.of(context).size.width * 0.15,
|
||||||
|
borderColor: const Color(0xFFE2E8F0),
|
||||||
|
highlightColor: const Color(0xFF50A398),
|
||||||
|
isdense: true,
|
||||||
|
errFieldHgt: 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildShortName() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('Short Name *', style: _textStyle),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
ThemedFormInlineField(
|
||||||
|
controller: controllers['code']!,
|
||||||
|
validator: (value) => Validators.requiredField(value, 'code'),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9\-_ ]')),
|
||||||
|
],
|
||||||
|
txtwidth: MediaQuery.of(context).size.width * 0.15,
|
||||||
|
borderColor: const Color(0xFFE2E8F0),
|
||||||
|
highlightColor: const Color(0xFF50A398),
|
||||||
|
isdense: true,
|
||||||
|
errFieldHgt: 0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _textStyle = GoogleFonts.inter(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
);
|
||||||
|
}
|
||||||
322
lib/presentation/screens/Masters/Insurers/insurerList.dart
Normal file
322
lib/presentation/screens/Masters/Insurers/insurerList.dart
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
|
||||||
|
import 'package:nhance_partner/presentation/screens/Masters/Insurers/insurer.dart';
|
||||||
|
|
||||||
|
import '../../../../core/routing/routes.dart';
|
||||||
|
import '../../../../core/services/api_service.dart';
|
||||||
|
import '../../../../data/utils/Pagination.dart';
|
||||||
|
import '../../../layouts/main_layout.dart';
|
||||||
|
import '../../../layouts/responsive_layout.dart';
|
||||||
|
import '../../../providers/manager_provider.dart';
|
||||||
|
import '../../../themes/indicators/export_btn.dart';
|
||||||
|
import '../../../themes/indicators/search_field_theme.dart';
|
||||||
|
|
||||||
|
class InsurerList extends ConsumerStatefulWidget {
|
||||||
|
const InsurerList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<InsurerList> createState() => InsurerListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsurerListState extends ConsumerState<InsurerList> {
|
||||||
|
int currentPage = 1;
|
||||||
|
int itemsPerPage = 10;
|
||||||
|
late ApiService apiService;
|
||||||
|
dynamic managerId;
|
||||||
|
dynamic role;
|
||||||
|
dynamic prefid;
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> getInsurerData = [];
|
||||||
|
List<Map<String, dynamic>> filteredData = [];
|
||||||
|
bool isLoading = false;
|
||||||
|
|
||||||
|
Map<String, dynamic>? selectedInsurer;
|
||||||
|
dynamic selectedId;
|
||||||
|
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
apiService = ApiService();
|
||||||
|
|
||||||
|
Future.microtask(() {
|
||||||
|
prefid = ref.read(managerIdProvider);
|
||||||
|
role = ref.read(userRoleProvider);
|
||||||
|
if (prefid != null && role != null) {
|
||||||
|
getInsurers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dynamic> get _paginatedData {
|
||||||
|
final sortedData = [...filteredData]
|
||||||
|
..sort((a, b) {
|
||||||
|
final idA = int.tryParse(a['id']?.toString() ?? '') ?? 0;
|
||||||
|
final idB = int.tryParse(b['id']?.toString() ?? '') ?? 0;
|
||||||
|
return idB.compareTo(idA);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sortedData.isEmpty) return [];
|
||||||
|
|
||||||
|
final maxPage = (sortedData.length / itemsPerPage).ceil();
|
||||||
|
final safePage = currentPage.clamp(1, maxPage);
|
||||||
|
|
||||||
|
final startIndex = (safePage - 1) * itemsPerPage;
|
||||||
|
final endIndex = (startIndex + itemsPerPage).clamp(0, sortedData.length);
|
||||||
|
|
||||||
|
return sortedData.sublist(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEdit(Map<String, dynamic> item) {
|
||||||
|
setState(() {
|
||||||
|
selectedId = item['id'];
|
||||||
|
selectedInsurer = item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void filterData(String query) {
|
||||||
|
setState(() {
|
||||||
|
final q = query.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (q.isEmpty) {
|
||||||
|
filteredData = List.from(getInsurerData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredData = getInsurerData.where((item) {
|
||||||
|
final name = (item['name'] ?? '').toString().toLowerCase();
|
||||||
|
final code = (item['short_name'] ?? '').toString().toLowerCase();
|
||||||
|
|
||||||
|
return name.contains(q) || code.contains(q);
|
||||||
|
}).toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getInsurers() async {
|
||||||
|
setState(() => isLoading = true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await apiService.fetchMasterDropDown('Insurers');
|
||||||
|
|
||||||
|
if (response['status'] == 200 || response['status'] == 'success') {
|
||||||
|
setState(() {
|
||||||
|
getInsurerData = List<Map<String, dynamic>>.from(response['data']);
|
||||||
|
filteredData = List.from(getInsurerData);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getInsurerData = [];
|
||||||
|
filteredData = [];
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
getInsurerData = [];
|
||||||
|
filteredData = [];
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => isLoading = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
managerId = ref.watch(managerIdProvider);
|
||||||
|
|
||||||
|
return MainLayout(
|
||||||
|
title: 'Insurer',
|
||||||
|
body: SelectionArea(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => context.go(AppRoutes.dashboard),
|
||||||
|
child: Text(
|
||||||
|
'Insurer',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Insurer(
|
||||||
|
key: ValueKey(selectedId),
|
||||||
|
data: selectedInsurer,
|
||||||
|
id: selectedId,
|
||||||
|
onSubmit: () {
|
||||||
|
getInsurers();
|
||||||
|
setState(() {
|
||||||
|
selectedInsurer = null;
|
||||||
|
selectedId = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
ThemedSearchField(
|
||||||
|
hintText: 'Search',
|
||||||
|
backgroundColor: const Color(0xFFFFFFFF),
|
||||||
|
txtHeight: 30,
|
||||||
|
onChanged: filterData,
|
||||||
|
controller: _searchController,
|
||||||
|
txtwidth: MediaQuery.of(context).size.width * 0.15,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
ExportBtn(
|
||||||
|
sheetName: 'Insurer',
|
||||||
|
fileName: 'insurer_list',
|
||||||
|
txt: !ResponsiveLayout.isMobile(context),
|
||||||
|
data: filteredData,
|
||||||
|
displayHeaders: [
|
||||||
|
'S.No.',
|
||||||
|
'Insurer',
|
||||||
|
'Short Name',
|
||||||
|
],
|
||||||
|
keys: ['sno', 'name', 'short_name'],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFF1F5F9),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 16,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 100, child: Text('S.No.', style: _headerStyle)),
|
||||||
|
Expanded(flex: 2, child: Text('Insurer', style: _headerStyle)),
|
||||||
|
Expanded(flex: 1, child: Text('Short Name', style: _headerStyle)),
|
||||||
|
Expanded(flex: 1, child: Text('Action', style: _headerStyle)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Colors.white,
|
||||||
|
child: _buildDataTable(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PaginationControls(
|
||||||
|
currentPage: currentPage,
|
||||||
|
itemsPerPage: itemsPerPage,
|
||||||
|
totalItems: filteredData.length,
|
||||||
|
onPageChanged: (page) => setState(() => currentPage = page),
|
||||||
|
onItemsPerPageChanged: (items) {
|
||||||
|
setState(() {
|
||||||
|
itemsPerPage = items;
|
||||||
|
currentPage = 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDataTable() {
|
||||||
|
if (isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredData.isEmpty) {
|
||||||
|
return const SizedBox(
|
||||||
|
height: 50,
|
||||||
|
child: Center(child: Text('No available data')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final sortedData = [..._paginatedData];
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: sortedData.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final startIndex = ((currentPage - 1) * itemsPerPage);
|
||||||
|
final item = sortedData[index];
|
||||||
|
final sno = startIndex + index + 1;
|
||||||
|
return _buildDataRow(item, sno);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDataRow(Map<String, dynamic> item, int sno) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 16),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(color: Colors.blueGrey, width: 0.15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 100, child: Text('$sno', style: _dataBold)),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(item['name'] ?? '-', style: _dataBold),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Text(item['short_name'] ?? '-', style: _dataBold),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit',
|
||||||
|
child: IconButton(
|
||||||
|
icon: Image.asset(
|
||||||
|
'assets/miscellaneous/Edit.png',
|
||||||
|
height: 12,
|
||||||
|
width: 15,
|
||||||
|
),
|
||||||
|
onPressed: () => handleEdit(item),
|
||||||
|
splashRadius: 28,
|
||||||
|
hoverColor: Colors.black12,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _dataBold = GoogleFonts.inter(
|
||||||
|
fontSize: 11.5,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: const Color(0xFF000000),
|
||||||
|
);
|
||||||
|
|
||||||
|
static final _headerStyle = GoogleFonts.poppins(
|
||||||
|
fontSize: 11.2,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: const Color(0xFF1E293B),
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -37,6 +37,12 @@ class DateFilterRowPayout extends ConsumerStatefulWidget {
|
|||||||
/// When `null`, legacy: date range and partner row together.
|
/// When `null`, legacy: date range and partner row together.
|
||||||
final bool? policyTableHasRows;
|
final bool? policyTableHasRows;
|
||||||
|
|
||||||
|
/// Optional per-column table search fields (shown between date range and partner).
|
||||||
|
final Widget? tableSearchFields;
|
||||||
|
|
||||||
|
/// When true, [getAgentUnusedCommissionList] receives [startController]/[endController] dates.
|
||||||
|
final bool passDateRangeToAgentList;
|
||||||
|
|
||||||
const DateFilterRowPayout({
|
const DateFilterRowPayout({
|
||||||
super.key,
|
super.key,
|
||||||
this.showBrokerFilter = true,
|
this.showBrokerFilter = true,
|
||||||
@ -55,6 +61,8 @@ class DateFilterRowPayout extends ConsumerStatefulWidget {
|
|||||||
this.isMobile = false,
|
this.isMobile = false,
|
||||||
this.dataFrom,
|
this.dataFrom,
|
||||||
this.policyTableHasRows,
|
this.policyTableHasRows,
|
||||||
|
this.tableSearchFields,
|
||||||
|
this.passDateRangeToAgentList = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -115,7 +123,9 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
|
|
||||||
if (!widget.showBrokerFilter && managerId != null) {
|
if (!widget.showBrokerFilter && managerId != null) {
|
||||||
// Broker filter hidden: load partner list directly by manager.
|
// Broker filter hidden: load partner list directly by manager.
|
||||||
getAgentList(managerId);
|
if (!_shouldDeferAgentListUntilDateRange()) {
|
||||||
|
getAgentList(managerId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,7 +165,35 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _hasSelectedDateRange() {
|
||||||
|
return widget.startController.text.trim().isNotEmpty &&
|
||||||
|
widget.endController.text.trim().isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldDeferAgentListUntilDateRange() {
|
||||||
|
return widget.passDateRangeToAgentList && !_hasSelectedDateRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _agentListFromDate() {
|
||||||
|
if (!widget.passDateRangeToAgentList) return null;
|
||||||
|
final value = widget.startController.text.trim();
|
||||||
|
return value.isEmpty ? null : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _agentListToDate() {
|
||||||
|
if (!widget.passDateRangeToAgentList) return null;
|
||||||
|
final value = widget.endController.text.trim();
|
||||||
|
return value.isEmpty ? null : value;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getAgentList(id) async {
|
Future<void> getAgentList(id) async {
|
||||||
|
if (_shouldDeferAgentListUntilDateRange()) {
|
||||||
|
setState(() {
|
||||||
|
getPartnerData = [];
|
||||||
|
filteredPartnerData = [];
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
print('getAgentListData called');
|
print('getAgentListData called');
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -163,7 +201,11 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await apiService.fetchAgentUnusedCommissionList(id);
|
final response = await apiService.fetchAgentUnusedCommissionList(
|
||||||
|
id,
|
||||||
|
fromDate: _agentListFromDate(),
|
||||||
|
toDate: _agentListToDate(),
|
||||||
|
);
|
||||||
print('getAgentListData called response');
|
print('getAgentListData called response');
|
||||||
print('get Agent- ${response['data']}');
|
print('get Agent- ${response['data']}');
|
||||||
if (response['status'] == 'success') {
|
if (response['status'] == 'success') {
|
||||||
@ -276,7 +318,15 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: 'Refresh',
|
message: 'Refresh',
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: widget.onRefresh,
|
onPressed: () {
|
||||||
|
if (widget.passDateRangeToAgentList) {
|
||||||
|
setState(() {
|
||||||
|
getPartnerData = [];
|
||||||
|
filteredPartnerData = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
widget.onRefresh();
|
||||||
|
},
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.refresh,
|
Icons.refresh,
|
||||||
size: 18,
|
size: 18,
|
||||||
@ -304,6 +354,10 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return [
|
return [
|
||||||
buildDateRangeFilter(context),
|
buildDateRangeFilter(context),
|
||||||
|
if (widget.tableSearchFields != null) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
widget.tableSearchFields!,
|
||||||
|
],
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: spacing,
|
spacing: spacing,
|
||||||
@ -315,6 +369,10 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
buildDateRangeFilter(context),
|
buildDateRangeFilter(context),
|
||||||
|
if (widget.tableSearchFields != null) ...[
|
||||||
|
SizedBox(width: spacing),
|
||||||
|
widget.tableSearchFields!,
|
||||||
|
],
|
||||||
SizedBox(width: spacing),
|
SizedBox(width: spacing),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
...partnerActionsRow,
|
...partnerActionsRow,
|
||||||
@ -349,7 +407,12 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
|||||||
txtheight: 32,
|
txtheight: 32,
|
||||||
lastDate: DateTime.now(),
|
lastDate: DateTime.now(),
|
||||||
rangeValidator: DateRangePickerField.defaultFilterValidator,
|
rangeValidator: DateRangePickerField.defaultFilterValidator,
|
||||||
onRangeSelected: (_) => Future.microtask(() => widget.onFilter()),
|
onRangeSelected: (_) => Future.microtask(() {
|
||||||
|
if (widget.passDateRangeToAgentList && managerId != null) {
|
||||||
|
getAgentList(managerId);
|
||||||
|
}
|
||||||
|
widget.onFilter();
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import '../../layouts/responsive_layout.dart';
|
|||||||
import '../../providers/manager_provider.dart';
|
import '../../providers/manager_provider.dart';
|
||||||
import '../../providers/userRoleProvider.dart';
|
import '../../providers/userRoleProvider.dart';
|
||||||
import '../../themes/indicators/export_btn.dart';
|
import '../../themes/indicators/export_btn.dart';
|
||||||
|
import '../../themes/indicators/search_field_theme.dart';
|
||||||
import 'custom_dateRange.dart';
|
import 'custom_dateRange.dart';
|
||||||
|
|
||||||
class PayOutDetails extends ConsumerStatefulWidget {
|
class PayOutDetails extends ConsumerStatefulWidget {
|
||||||
@ -69,6 +70,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
'invoiceDate',
|
'invoiceDate',
|
||||||
'invoiceStatus',
|
'invoiceStatus',
|
||||||
'utrNumber',
|
'utrNumber',
|
||||||
|
'tableSearch',
|
||||||
];
|
];
|
||||||
|
|
||||||
// --------------------------
|
// --------------------------
|
||||||
@ -218,43 +220,38 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void filterPolicyData(String query) {
|
String _partnerDisplayText(Map<String, dynamic> item) {
|
||||||
final lowerQuery = query.toLowerCase();
|
if (item['agent_code'] != null && item['agent_name'] != null) {
|
||||||
|
return '${item['agent_code']} - ${item['agent_name']}';
|
||||||
|
}
|
||||||
|
return (item['agent_name'] ?? '').toString();
|
||||||
|
}
|
||||||
|
|
||||||
print('PD => filterPolicyData - $lowerQuery');
|
bool _matchesTableSearch(Map<String, dynamic> item, String query) {
|
||||||
|
if (query.isEmpty) return true;
|
||||||
|
|
||||||
|
final searchableFields = [
|
||||||
|
(item['policy_no'] ?? '').toString(),
|
||||||
|
_formatDate(item['issued_date']?.toString() ?? ''),
|
||||||
|
_partnerDisplayText(item),
|
||||||
|
(item['customer_name'] ?? '').toString(),
|
||||||
|
_amountText(item['premium_amount']),
|
||||||
|
(item['premium_amount'] ?? '').toString(),
|
||||||
|
];
|
||||||
|
|
||||||
|
return searchableFields.any(
|
||||||
|
(field) => field.toLowerCase().contains(query),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyTableFilters() {
|
||||||
|
final query = controllers['tableSearch']?.text.trim().toLowerCase() ?? '';
|
||||||
|
|
||||||
print('PD => filteredPolicies1 - $filteredPolicies');
|
|
||||||
setState(() {
|
setState(() {
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
if (query.trim().isEmpty) {
|
filteredPolicies = masterPolicies
|
||||||
print('PD => filteredPolicies2');
|
.where((item) => _matchesTableSearch(item, query))
|
||||||
filteredPolicies = List.from(masterPolicies);
|
.toList();
|
||||||
return;
|
|
||||||
}
|
|
||||||
print('PD => filteredPolicies3');
|
|
||||||
filteredPolicies = masterPolicies.where((item) {
|
|
||||||
return (item['policy_no'] ?? '').toString().toLowerCase().contains(
|
|
||||||
lowerQuery,
|
|
||||||
) ||
|
|
||||||
(item['customer_name'] ?? '').toString().toLowerCase().contains(
|
|
||||||
lowerQuery,
|
|
||||||
) ||
|
|
||||||
(item['agent_name'] ?? '').toString().toLowerCase().contains(
|
|
||||||
lowerQuery,
|
|
||||||
) ||
|
|
||||||
(item['insurer_name'] ?? '').toString().toLowerCase().contains(
|
|
||||||
lowerQuery,
|
|
||||||
) ||
|
|
||||||
(item['premium_amount'] ?? '').toString().toLowerCase().contains(
|
|
||||||
lowerQuery,
|
|
||||||
) ||
|
|
||||||
(item['commission_amount'] ?? '').toString().toLowerCase().contains(
|
|
||||||
lowerQuery,
|
|
||||||
) ||
|
|
||||||
_formatDate(item['issued_date']?.toString() ?? '')
|
|
||||||
.toLowerCase()
|
|
||||||
.contains(lowerQuery);
|
|
||||||
}).toList();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,6 +266,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
selectedPolicies = {};
|
selectedPolicies = {};
|
||||||
totalPolicies = '';
|
totalPolicies = '';
|
||||||
totalCommission = '';
|
totalCommission = '';
|
||||||
|
masterPolicies = [];
|
||||||
filteredPolicies = [];
|
filteredPolicies = [];
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
_disposeCommissionControllers();
|
_disposeCommissionControllers();
|
||||||
@ -407,21 +405,17 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
if (response['status'] == 'success') {
|
if (response['status'] == 'success') {
|
||||||
print('PD =>API Data response - $response');
|
print('PD =>API Data response - $response');
|
||||||
setState(() {
|
setState(() {
|
||||||
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
masterPolicies = List<Map<String, dynamic>>.from(response['data']);
|
||||||
_rebuildCommissionControllers(filteredPolicies);
|
filteredPolicies = List.from(masterPolicies);
|
||||||
|
_rebuildCommissionControllers(masterPolicies);
|
||||||
hasFetchedTableData = true;
|
hasFetchedTableData = true;
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
print('PD =>API Data - $filteredPolicies');
|
print('PD =>API Data - $filteredPolicies');
|
||||||
// filteredPolicies = allPolicies.where((p) {
|
|
||||||
// if (p["agentId"] != selectedAgentId) return false;
|
|
||||||
// if (policyTillDate != null &&
|
|
||||||
// DateTime.parse(p["date"]).isAfter(policyTillDate!))
|
|
||||||
// return false;
|
|
||||||
// return true;
|
|
||||||
// }).toList();
|
|
||||||
});
|
});
|
||||||
|
applyTableFilters();
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
masterPolicies = [];
|
||||||
filteredPolicies = [];
|
filteredPolicies = [];
|
||||||
_rebuildCommissionControllers(filteredPolicies);
|
_rebuildCommissionControllers(filteredPolicies);
|
||||||
hasFetchedTableData = true;
|
hasFetchedTableData = true;
|
||||||
@ -431,6 +425,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('PD =>Exception occurred: $e');
|
print('PD =>Exception occurred: $e');
|
||||||
setState(() {
|
setState(() {
|
||||||
|
masterPolicies = [];
|
||||||
filteredPolicies = [];
|
filteredPolicies = [];
|
||||||
_rebuildCommissionControllers(filteredPolicies);
|
_rebuildCommissionControllers(filteredPolicies);
|
||||||
hasFetchedTableData = false;
|
hasFetchedTableData = false;
|
||||||
@ -900,9 +895,13 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
role: roleId,
|
role: roleId,
|
||||||
id: userId,
|
id: userId,
|
||||||
showBrokerFilter: false,
|
showBrokerFilter: false,
|
||||||
|
passDateRangeToAgentList: true,
|
||||||
policyTableHasRows: filteredPolicies.isNotEmpty,
|
policyTableHasRows: filteredPolicies.isNotEmpty,
|
||||||
selectedParnter: selectedAgentId,
|
selectedParnter: selectedAgentId,
|
||||||
selectedBroker: selectedBrokerID,
|
selectedBroker: selectedBrokerID,
|
||||||
|
tableSearchFields: hasFetchedTableData
|
||||||
|
? _buildPolicyTableSearchFields(context)
|
||||||
|
: null,
|
||||||
|
|
||||||
onPartnerChanges: (val) {
|
onPartnerChanges: (val) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -1174,6 +1173,24 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildPolicyTableSearchFields(BuildContext context) {
|
||||||
|
final width = ResponsiveLayout.isMobile(context)
|
||||||
|
? MediaQuery.of(context).size.width * 0.55
|
||||||
|
: MediaQuery.of(context).size.width * 0.2;
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: width,
|
||||||
|
child: ThemedSearchField(
|
||||||
|
hintText: 'Search',
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
txtHeight: 30,
|
||||||
|
controller: controllers['tableSearch']!,
|
||||||
|
txtwidth: width,
|
||||||
|
onChanged: (_) => applyTableFilters(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildDataTable(BuildContext context) {
|
Widget _buildDataTable(BuildContext context) {
|
||||||
if (filteredPolicies.isEmpty) {
|
if (filteredPolicies.isEmpty) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
@ -1205,6 +1222,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
Widget _buildDataRow(Map<String, dynamic> item, int sno) {
|
Widget _buildDataRow(Map<String, dynamic> item, int sno) {
|
||||||
final int id = int.tryParse(item["policy_id"]?.toString() ?? "0") ?? 0;
|
final int id = int.tryParse(item["policy_id"]?.toString() ?? "0") ?? 0;
|
||||||
final bool isSelected = selectedPolicies.contains(id);
|
final bool isSelected = selectedPolicies.contains(id);
|
||||||
|
final partner = _partnerDisplayText(item);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -1274,9 +1292,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: _flexAgent,
|
flex: _flexAgent,
|
||||||
child: Text(
|
child: Text(
|
||||||
(item["agent_code"] != null && item["agent_name"] != null)
|
partner.isEmpty ? '-' : partner,
|
||||||
? '${item["agent_code"]} - ${item["agent_name"]}'
|
|
||||||
: (item["agent_name"] ?? '-'),
|
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: _tableDataStyle,
|
style: _tableDataStyle,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@ -1345,9 +1361,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
|||||||
'sno': 0,
|
'sno': 0,
|
||||||
'policy_no': p['policy_no'],
|
'policy_no': p['policy_no'],
|
||||||
'policy_date': _formatDate((p['issued_date'] ?? '').toString()),
|
'policy_date': _formatDate((p['issued_date'] ?? '').toString()),
|
||||||
'partner': (p["agent_code"] != null && p["agent_name"] != null)
|
'partner': _partnerDisplayText(p).isEmpty
|
||||||
? '${p["agent_code"]} - ${p["agent_name"]}'
|
? '-'
|
||||||
: (p["agent_name"] ?? '-').toString(),
|
: _partnerDisplayText(p),
|
||||||
'customer': p['customer_name'],
|
'customer': p['customer_name'],
|
||||||
'premium': _amountText(p['premium_amount']),
|
'premium': _amountText(p['premium_amount']),
|
||||||
'payout': _amountText(p['commission_amount']),
|
'payout': _amountText(p['commission_amount']),
|
||||||
|
|||||||
@ -24,6 +24,8 @@ class PayoutList extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PayoutListState extends ConsumerState<PayoutList> {
|
class _PayoutListState extends ConsumerState<PayoutList> {
|
||||||
|
static const double _payoutFilterFieldHeight = 35;
|
||||||
|
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
int itemsPerPage = 10;
|
int itemsPerPage = 10;
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
@ -35,7 +37,13 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
|||||||
late ApiService apiService;
|
late ApiService apiService;
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
Map<String, TextEditingController> controllers = {};
|
Map<String, TextEditingController> controllers = {};
|
||||||
List<String> tabHeader = ['startDate', 'endDate'];
|
List<String> tabHeader = [
|
||||||
|
'startDate',
|
||||||
|
'endDate',
|
||||||
|
'agentName',
|
||||||
|
'agentCode',
|
||||||
|
'policyNo',
|
||||||
|
];
|
||||||
dynamic SelectedStatus;
|
dynamic SelectedStatus;
|
||||||
dynamic SelectedStaffId;
|
dynamic SelectedStaffId;
|
||||||
DateTime _paymentDate = DateTime.now();
|
DateTime _paymentDate = DateTime.now();
|
||||||
@ -75,6 +83,9 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
|||||||
final response = await apiService.getPayoutList(
|
final response = await apiService.getPayoutList(
|
||||||
fromDate: from.isNotEmpty ? from : null,
|
fromDate: from.isNotEmpty ? from : null,
|
||||||
toDate: to.isNotEmpty ? to : null,
|
toDate: to.isNotEmpty ? to : null,
|
||||||
|
agentName: controllers['agentName']?.text,
|
||||||
|
agentCode: controllers['agentCode']?.text,
|
||||||
|
policyNo: controllers['policyNo']?.text,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response['status'] == 'success') {
|
if (response['status'] == 'success') {
|
||||||
@ -157,13 +168,11 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
|||||||
|
|
||||||
void refrshfilterDateRange() {
|
void refrshfilterDateRange() {
|
||||||
setState(() {
|
setState(() {
|
||||||
// SelectedStaffId = null;
|
|
||||||
controllers['startDate']!.clear();
|
controllers['startDate']!.clear();
|
||||||
controllers['endDate']!.clear();
|
controllers['endDate']!.clear();
|
||||||
|
controllers['agentName']!.clear();
|
||||||
controllers['startDate']?.text = '';
|
controllers['agentCode']!.clear();
|
||||||
controllers['endDate']?.text = '';
|
controllers['policyNo']!.clear();
|
||||||
// Reset the FormField validation
|
|
||||||
_formKey.currentState?.reset();
|
_formKey.currentState?.reset();
|
||||||
});
|
});
|
||||||
getPayoutList();
|
getPayoutList();
|
||||||
@ -174,13 +183,16 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
|||||||
final q = query.toLowerCase();
|
final q = query.toLowerCase();
|
||||||
setState(() {
|
setState(() {
|
||||||
filteredData = getPayoutData.where((item) {
|
filteredData = getPayoutData.where((item) {
|
||||||
// Search matches visible table columns only (broker / referer / updated hidden).
|
// Search matches visible table columns only.
|
||||||
final invoiceDateSearch = _formatDateSafe(item['invoice_date'])
|
final invoiceDateSearch = _formatDateSafe(item['invoice_date'])
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
final statusSearch =
|
final statusSearch =
|
||||||
_exportPayoutStatusLabel(item['payout_status']).toLowerCase();
|
_exportPayoutStatusLabel(item['payout_status']).toLowerCase();
|
||||||
|
|
||||||
return invoiceDateSearch.contains(q) ||
|
return invoiceDateSearch.contains(q) ||
|
||||||
|
(item['agent_name'] ?? '-').toString().toLowerCase().contains(q) ||
|
||||||
|
(item['agent_code'] ?? '-').toString().toLowerCase().contains(q) ||
|
||||||
|
(item['policy_no'] ?? '-').toString().toLowerCase().contains(q) ||
|
||||||
(item['invoice_no'] ?? '-').toLowerCase().contains(q) ||
|
(item['invoice_no'] ?? '-').toLowerCase().contains(q) ||
|
||||||
(item['invoice_amount_indian_format'] ?? '-')
|
(item['invoice_amount_indian_format'] ?? '-')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@ -654,9 +666,122 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _payoutFilterField({
|
||||||
|
required String hint,
|
||||||
|
required TextEditingController controller,
|
||||||
|
required double width,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
width: width,
|
||||||
|
height: _payoutFilterFieldHeight,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
hintText: hint,
|
||||||
|
hintStyle: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: const Color(0xFF64748B),
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _payoutFilterActions() {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Tooltip(
|
||||||
|
message: 'Filter',
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.search_rounded,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF94A3B8),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
filterDateRange();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: 'Refresh',
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: refrshfilterDateRange,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.refresh,
|
||||||
|
size: 18,
|
||||||
|
color: Color(0xFF94A3B8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _payoutTextFilters(BuildContext context) {
|
||||||
|
final fieldWidth = ResponsiveLayout.isMobile(context) ? 120.0 : 140.0;
|
||||||
|
final isMobile = ResponsiveLayout.isMobile(context);
|
||||||
|
final fields = [
|
||||||
|
_payoutFilterField(
|
||||||
|
hint: 'Agent Name',
|
||||||
|
controller: controllers['agentName']!,
|
||||||
|
width: fieldWidth,
|
||||||
|
),
|
||||||
|
_payoutFilterField(
|
||||||
|
hint: 'Agent Code',
|
||||||
|
controller: controllers['agentCode']!,
|
||||||
|
width: fieldWidth,
|
||||||
|
),
|
||||||
|
_payoutFilterField(
|
||||||
|
hint: 'Policy No',
|
||||||
|
controller: controllers['policyNo']!,
|
||||||
|
width: fieldWidth,
|
||||||
|
),
|
||||||
|
_payoutFilterActions(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: fields,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
for (var i = 0; i < fields.length; i++) ...[
|
||||||
|
if (i > 0) const SizedBox(width: 8),
|
||||||
|
fields[i],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _payoutInvoiceDateFilter(BuildContext context) {
|
Widget _payoutInvoiceDateFilter(BuildContext context) {
|
||||||
return DateFilterRow(
|
final isMobile = ResponsiveLayout.isMobile(context);
|
||||||
|
final dateFilter = DateFilterRow(
|
||||||
compactDateFiltersOnly: true,
|
compactDateFiltersOnly: true,
|
||||||
|
hideFilterActions: true,
|
||||||
dataFrom: 'Payout',
|
dataFrom: 'Payout',
|
||||||
role: roleId,
|
role: roleId,
|
||||||
id: userId,
|
id: userId,
|
||||||
@ -665,10 +790,31 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
|||||||
endController: controllers['endDate']!,
|
endController: controllers['endDate']!,
|
||||||
onStatusChanged: (_) {},
|
onStatusChanged: (_) {},
|
||||||
formKey: _formKey,
|
formKey: _formKey,
|
||||||
isMobile: ResponsiveLayout.isMobile(context),
|
isMobile: isMobile,
|
||||||
onFilter: filterDateRange,
|
onFilter: filterDateRange,
|
||||||
onRefresh: refrshfilterDateRange,
|
onRefresh: refrshfilterDateRange,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
dateFilter,
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_payoutTextFilters(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
dateFilter,
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_payoutTextFilters(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context) {
|
Widget _buildContent(BuildContext context) {
|
||||||
|
|||||||
@ -169,6 +169,8 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
|||||||
// int selectedIndex = 1;
|
// int selectedIndex = 1;
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
int autoRefreshIndex = 0;
|
int autoRefreshIndex = 0;
|
||||||
|
int _staffListRequestSeq = 0;
|
||||||
|
int _staffListInFlight = 0;
|
||||||
|
|
||||||
final List<String> enquiryTabs = ["In Progress", "Completed"];
|
final List<String> enquiryTabs = ["In Progress", "Completed"];
|
||||||
|
|
||||||
@ -452,7 +454,6 @@ if (managerId != null &&
|
|||||||
}
|
}
|
||||||
|
|
||||||
void filterDateRange() {
|
void filterDateRange() {
|
||||||
// ✅ Call your API
|
|
||||||
_searchStaffController.clear();
|
_searchStaffController.clear();
|
||||||
getStaffList(
|
getStaffList(
|
||||||
managerId,
|
managerId,
|
||||||
@ -462,6 +463,42 @@ if (managerId != null &&
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isCompletedEnquiryStatus(dynamic status) {
|
||||||
|
return status?.toString().trim().toLowerCase() == 'completed';
|
||||||
|
}
|
||||||
|
|
||||||
|
void _splitEnquiryLists(List<Map<String, dynamic>> data) {
|
||||||
|
inProgressData = data
|
||||||
|
.where((item) => !_isCompletedEnquiryStatus(item['enquiry_status']))
|
||||||
|
.toList();
|
||||||
|
completedData = data
|
||||||
|
.where((item) => _isCompletedEnquiryStatus(item['enquiry_status']))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _syncFilteredDataForCurrentTab({bool resetCompletedPage = false}) {
|
||||||
|
filteredData = selectedIndex == 0
|
||||||
|
? List<Map<String, dynamic>>.from(inProgressData)
|
||||||
|
: List<Map<String, dynamic>>.from(completedData);
|
||||||
|
|
||||||
|
if (selectedIndex != 1) return;
|
||||||
|
|
||||||
|
if (resetCompletedPage || filteredData.isEmpty) {
|
||||||
|
completedCurrentPage = 1;
|
||||||
|
} else {
|
||||||
|
final maxPage = (filteredData.length / completedItemsPerPage)
|
||||||
|
.ceil()
|
||||||
|
.clamp(1, 999999);
|
||||||
|
if (completedCurrentPage > maxPage) {
|
||||||
|
completedCurrentPage = maxPage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCompletedPages = filteredData.isEmpty
|
||||||
|
? 1
|
||||||
|
: (filteredData.length / completedItemsPerPage).ceil().clamp(1, 999999);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
refreshSub.close();
|
refreshSub.close();
|
||||||
@ -520,31 +557,19 @@ if (managerId != null &&
|
|||||||
List<Map<String, dynamic>> get paginatedCompletedData {
|
List<Map<String, dynamic>> get paginatedCompletedData {
|
||||||
final dataToUse = filteredData;
|
final dataToUse = filteredData;
|
||||||
|
|
||||||
if (dataToUse.isEmpty) return [];
|
if (dataToUse.isEmpty || selectedIndex != 1) return dataToUse;
|
||||||
|
|
||||||
if (selectedIndex == 1) {
|
final maxPage = (dataToUse.length / completedItemsPerPage)
|
||||||
totalCompletedPages = (dataToUse.length / completedItemsPerPage)
|
.ceil()
|
||||||
.ceil()
|
.clamp(1, 999999);
|
||||||
.clamp(1, double.infinity)
|
final safePage = completedCurrentPage.clamp(1, maxPage);
|
||||||
.toInt();
|
final startIndex = (safePage - 1) * completedItemsPerPage;
|
||||||
|
final endIndex = (startIndex + completedItemsPerPage).clamp(
|
||||||
|
0,
|
||||||
|
dataToUse.length,
|
||||||
|
);
|
||||||
|
|
||||||
final startIndex = (completedCurrentPage - 1) * completedItemsPerPage;
|
return dataToUse.sublist(startIndex, endIndex);
|
||||||
|
|
||||||
// 🚨 Guard: page out of range after search
|
|
||||||
if (startIndex >= dataToUse.length) {
|
|
||||||
completedCurrentPage = 1;
|
|
||||||
return dataToUse.take(completedItemsPerPage).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
final endIndex = (startIndex + completedItemsPerPage).clamp(
|
|
||||||
0,
|
|
||||||
dataToUse.length,
|
|
||||||
);
|
|
||||||
|
|
||||||
return dataToUse.sublist(startIndex, endIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataToUse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> autoRefrshfilterDateRange() async {
|
Future<void> autoRefrshfilterDateRange() async {
|
||||||
@ -706,6 +731,10 @@ if (managerId != null &&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _syncStaffListLoader() {
|
||||||
|
isLoadingStaffList = isActionable && _staffListInFlight > 0;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getStaffList(int managerId,
|
Future<void> getStaffList(int managerId,
|
||||||
role, {
|
role, {
|
||||||
String fromDate = '',
|
String fromDate = '',
|
||||||
@ -714,8 +743,10 @@ if (managerId != null &&
|
|||||||
String SelectedStaffId = '',
|
String SelectedStaffId = '',
|
||||||
}) async {
|
}) async {
|
||||||
print('A613 => Fns called => $managerId | $role');
|
print('A613 => Fns called => $managerId | $role');
|
||||||
|
final requestId = ++_staffListRequestSeq;
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoadingStaffList = true;
|
_staffListInFlight++;
|
||||||
|
_syncStaffListLoader();
|
||||||
});
|
});
|
||||||
print('A713 => Fns called => $managerId | $role');
|
print('A713 => Fns called => $managerId | $role');
|
||||||
|
|
||||||
@ -818,6 +849,11 @@ if (managerId != null &&
|
|||||||
print('ToDate : $toDate');
|
print('ToDate : $toDate');
|
||||||
print('A1213 => Fns called => $managerId | $role');
|
print('A1213 => Fns called => $managerId | $role');
|
||||||
|
|
||||||
|
if (requestId != _staffListRequestSeq) {
|
||||||
|
print('Ignoring stale enquiry list response: $requestId');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_resetKeyEnable = false;
|
_resetKeyEnable = false;
|
||||||
|
|
||||||
@ -827,36 +863,30 @@ if (managerId != null &&
|
|||||||
if (data is List) {
|
if (data is List) {
|
||||||
getStaffData = List<Map<String, dynamic>>.from(data);
|
getStaffData = List<Map<String, dynamic>>.from(data);
|
||||||
originalData = List<Map<String, dynamic>>.from(data);
|
originalData = List<Map<String, dynamic>>.from(data);
|
||||||
|
_splitEnquiryLists(originalData);
|
||||||
inProgressData = originalData
|
|
||||||
.where((item) => item['enquiry_status'] != 'Completed')
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
print('inProgressData - $inProgressData');
|
|
||||||
|
|
||||||
completedData = originalData
|
|
||||||
.where((item) => item['enquiry_status'] == 'Completed')
|
|
||||||
.toList();
|
|
||||||
print('completedData - $completedData');
|
|
||||||
|
|
||||||
filteredData = (selectedIndex == 0)
|
|
||||||
? List.from(inProgressData)
|
|
||||||
: List.from(completedData);
|
|
||||||
|
|
||||||
if (SelectedStatus == 'Completed') {
|
if (SelectedStatus == 'Completed') {
|
||||||
selectedIndex = 1;
|
selectedIndex = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_syncFilteredDataForCurrentTab(resetCompletedPage: true);
|
||||||
|
|
||||||
print('filteredData count: ${filteredData.length}');
|
print('filteredData count: ${filteredData.length}');
|
||||||
print('inProgressData count: ${inProgressData.length}');
|
print('inProgressData count: ${inProgressData.length}');
|
||||||
print('completedData count: ${completedData.length}');
|
print('completedData count: ${completedData.length}');
|
||||||
} else if (data is Map) {
|
} else if (data is Map) {
|
||||||
getStaffData = [Map<String, dynamic>.from(data)];
|
getStaffData = [Map<String, dynamic>.from(data)];
|
||||||
|
originalData = List.from(getStaffData);
|
||||||
|
inProgressData = [];
|
||||||
|
completedData = [];
|
||||||
|
filteredData = [];
|
||||||
} else {
|
} else {
|
||||||
getStaffData = [];
|
getStaffData = [];
|
||||||
|
originalData = [];
|
||||||
|
inProgressData = [];
|
||||||
|
completedData = [];
|
||||||
|
filteredData = [];
|
||||||
}
|
}
|
||||||
originalData = getStaffData;
|
|
||||||
filteredData = List.from(originalData);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ FIX CHANGE 2: Clear dashboardKey AFTER first successful load
|
// ✅ FIX CHANGE 2: Clear dashboardKey AFTER first successful load
|
||||||
@ -868,15 +898,22 @@ if (managerId != null &&
|
|||||||
print('✅ dashboardKey cleared after first successful load');
|
print('✅ dashboardKey cleared after first successful load');
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else if (requestId == _staffListRequestSeq) {
|
||||||
getStaffData = [];
|
setState(() {
|
||||||
originalData = [];
|
getStaffData = [];
|
||||||
|
originalData = [];
|
||||||
|
inProgressData = [];
|
||||||
|
completedData = [];
|
||||||
|
filteredData = [];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception occurred: $e');
|
print('Exception occurred: $e');
|
||||||
} finally {
|
} finally {
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoadingStaffList = false;
|
if (_staffListInFlight > 0) _staffListInFlight--;
|
||||||
|
_syncStaffListLoader();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1913,7 +1950,9 @@ if (managerId != null &&
|
|||||||
|
|
||||||
Widget _buildContent(BuildContext context) {
|
Widget _buildContent(BuildContext context) {
|
||||||
final isDesktop = !ResponsiveLayout.isMobile(context);
|
final isDesktop = !ResponsiveLayout.isMobile(context);
|
||||||
return Column(
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
@ -2005,6 +2044,7 @@ if (managerId != null &&
|
|||||||
isActionable = newValue;
|
isActionable = newValue;
|
||||||
userPreference =
|
userPreference =
|
||||||
newValue; // Save this as the new "normal"
|
newValue; // Save this as the new "normal"
|
||||||
|
_syncStaffListLoader();
|
||||||
});
|
});
|
||||||
final prefs =
|
final prefs =
|
||||||
await SharedPreferences.getInstance();
|
await SharedPreferences.getInstance();
|
||||||
@ -2104,6 +2144,7 @@ if (managerId != null &&
|
|||||||
// CASE 2: Search cleared -> Restore the status from memory
|
// CASE 2: Search cleared -> Restore the status from memory
|
||||||
isActionable = userPreference;
|
isActionable = userPreference;
|
||||||
}
|
}
|
||||||
|
_syncStaffListLoader();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -2167,6 +2208,19 @@ if (managerId != null &&
|
|||||||
child: _buildDataTable(context),
|
child: _buildDataTable(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
if (isLoadingStaffList)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white.withOpacity(0.55),
|
||||||
|
child: const Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: Color(0xFF2E7D6E),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2175,6 +2229,7 @@ if (managerId != null &&
|
|||||||
'To be assigned': true,
|
'To be assigned': true,
|
||||||
'Assigned': true,
|
'Assigned': true,
|
||||||
'In progress': true,
|
'In progress': true,
|
||||||
|
'Completed': true,
|
||||||
};
|
};
|
||||||
|
|
||||||
Map<String, String> statusGroupMap = {
|
Map<String, String> statusGroupMap = {
|
||||||
@ -2211,17 +2266,17 @@ if (managerId != null &&
|
|||||||
grouped["To be assigned"] = [];
|
grouped["To be assigned"] = [];
|
||||||
grouped["Assigned"] = [];
|
grouped["Assigned"] = [];
|
||||||
grouped["In progress"] = [];
|
grouped["In progress"] = [];
|
||||||
} else {
|
|
||||||
grouped["Completed"] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var item in filteredDataByTab) {
|
for (final item in filteredDataByTab) {
|
||||||
final status = (item['enquiry_status'] ?? '').toString();
|
final status = (item['enquiry_status'] ?? '').toString();
|
||||||
if (grouped.containsKey(status)) {
|
if (grouped.containsKey(status)) {
|
||||||
grouped[status]!.add(item);
|
grouped[status]!.add(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grouped["Completed"] = List<Map<String, dynamic>>.from(filteredDataByTab);
|
||||||
return grouped;
|
return grouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2369,7 +2424,8 @@ if (managerId != null &&
|
|||||||
// Replace your _buildDataTableContent() method with this optimized version
|
// Replace your _buildDataTableContent() method with this optimized version
|
||||||
|
|
||||||
Widget _buildDataTableContent() {
|
Widget _buildDataTableContent() {
|
||||||
if (filteredData.isEmpty) {
|
final visibleRows = filteredDataByTab;
|
||||||
|
if (visibleRows.isEmpty) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
child: Center(child: Text('No available data')),
|
child: Center(child: Text('No available data')),
|
||||||
@ -3485,16 +3541,8 @@ if (managerId != null &&
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
print('TAPPED TAB');
|
|
||||||
selectedIndex = index;
|
selectedIndex = index;
|
||||||
|
_syncFilteredDataForCurrentTab(resetCompletedPage: index == 1);
|
||||||
filteredData = (index == 0) ? inProgressData : completedData;
|
|
||||||
if (index == 1) {
|
|
||||||
completedCurrentPage = 1;
|
|
||||||
// final newValue = false;
|
|
||||||
// isActionable = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_searchStaffController.clear();
|
_searchStaffController.clear();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class ThemedSearchField extends HookWidget {
|
|||||||
this.maxLength, // ✅ new
|
this.maxLength, // ✅ new
|
||||||
this.inputFormatters,
|
this.inputFormatters,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
|
this.showSearchIcon = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String hintText;
|
final String hintText;
|
||||||
@ -44,6 +45,7 @@ class ThemedSearchField extends HookWidget {
|
|||||||
final TextInputType? keyboardType;
|
final TextInputType? keyboardType;
|
||||||
final int? maxLength;
|
final int? maxLength;
|
||||||
final List<TextInputFormatter>? inputFormatters;
|
final List<TextInputFormatter>? inputFormatters;
|
||||||
|
final bool showSearchIcon;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -86,11 +88,16 @@ class ThemedSearchField extends HookWidget {
|
|||||||
// maxWidth: 25 + 16 + 10,
|
// maxWidth: 25 + 16 + 10,
|
||||||
// maxHeight: 25 + (8 * 2),
|
// maxHeight: 25 + (8 * 2),
|
||||||
// ),
|
// ),
|
||||||
suffixIcon: Padding(
|
suffixIcon: showSearchIcon
|
||||||
padding: const EdgeInsetsDirectional.only(start: 16, end: 10),
|
? Padding(
|
||||||
child: Icon(Icons.search, color: const Color(0xFF94A3B8), size: 18),
|
padding: const EdgeInsetsDirectional.only(start: 16, end: 10),
|
||||||
// child: Icon(Icons.search, color: const Color(0xFF686868), size: 20),
|
child: Icon(
|
||||||
),
|
Icons.search,
|
||||||
|
color: const Color(0xFF94A3B8),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
|||||||
@ -58,6 +58,8 @@ class DateFilterRow extends ConsumerStatefulWidget {
|
|||||||
/// When true, only start/end date fields and filter/refresh actions are shown
|
/// When true, only start/end date fields and filter/refresh actions are shown
|
||||||
/// (no status, staff, insurer, or partner controls).
|
/// (no status, staff, insurer, or partner controls).
|
||||||
final bool compactDateFiltersOnly;
|
final bool compactDateFiltersOnly;
|
||||||
|
/// When true, filter/refresh buttons are omitted (caller renders them elsewhere).
|
||||||
|
final bool hideFilterActions;
|
||||||
final role;
|
final role;
|
||||||
final id;
|
final id;
|
||||||
|
|
||||||
@ -82,6 +84,7 @@ class DateFilterRow extends ConsumerStatefulWidget {
|
|||||||
required this.id,
|
required this.id,
|
||||||
this.isMobile = false,
|
this.isMobile = false,
|
||||||
this.compactDateFiltersOnly = false,
|
this.compactDateFiltersOnly = false,
|
||||||
|
this.hideFilterActions = false,
|
||||||
this.dataFrom,
|
this.dataFrom,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -344,7 +347,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
// For mobile: buttons below fields
|
if (widget.hideFilterActions) return dateFields;
|
||||||
return [
|
return [
|
||||||
...dateFields,
|
...dateFields,
|
||||||
SizedBox(height: 10),
|
SizedBox(height: 10),
|
||||||
@ -352,6 +355,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
|||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
// For desktop: buttons inline with fields
|
// For desktop: buttons inline with fields
|
||||||
|
if (widget.hideFilterActions) return dateFields;
|
||||||
return [...dateFields, ...buttons];
|
return [...dateFields, ...buttons];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,8 +16,9 @@
|
|||||||
-->
|
-->
|
||||||
<!-- <base href="$FLUTTER_BASE_HREF"> -->
|
<!-- <base href="$FLUTTER_BASE_HREF"> -->
|
||||||
<base href="/partner/">
|
<base href="/partner/">
|
||||||
<!-- <base href="/nhance/partner/app/">-->
|
<!-- below one is dev-->
|
||||||
<!-- <base href="{Env.baseHref}">-->
|
<!-- <base href="/nhance/partner/app/">-->
|
||||||
|
<!-- <base href="{Env.baseHref}">-->
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user