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(
|
||||
'API_URL',
|
||||
// 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://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: 'http://localhost/nhance_partner_be/', /* localhost build (enable index.html line 19) */
|
||||
);
|
||||
// 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/Masters/Brokers/brokerList.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/StaffAttendance/attendanceAllDetails.dart';
|
||||
import '../../presentation/screens/StaffAttendance/individual_Attendance.dart';
|
||||
@ -381,6 +382,10 @@ final GoRouter appRouter = GoRouter(
|
||||
path: AppRoutes.paymentModeLst,
|
||||
builder: (context, state) => const PaymentLsit(),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.insurerLst,
|
||||
builder: (context, state) => const InsurerList(),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.gridList,
|
||||
builder: (context, state) => const GridListScreen(),
|
||||
|
||||
@ -55,6 +55,7 @@ class AppRoutes {
|
||||
static const String paymentModeLst = '/paymentMode';
|
||||
static const String endorsementTypeLst = '/endorsementTypeLst';
|
||||
static const String vehicleTypeLst = '/vehicleTypeLst';
|
||||
static const String insurerLst = '/insurerLst';
|
||||
static const String payoutList = '/payoutList';
|
||||
static const String payoutDetails = '/payoutDetails';
|
||||
static const String payoutDetailsView = '/payoutDetailsView';
|
||||
|
||||
@ -734,7 +734,10 @@ class ApiService {
|
||||
}
|
||||
else if (masterName == 'VehicleType') {
|
||||
url = Uri.parse('${Env.apiUrl}master/updateVehicleTypeStatus/$id');
|
||||
}
|
||||
}
|
||||
else if (masterName == 'Insurer') {
|
||||
url = Uri.parse('${Env.apiUrl}master/updateInsurerStatus/$id');
|
||||
}
|
||||
else {
|
||||
url = Uri.parse('${Env.apiUrl}master/updatePaymentModeStatus/$id');
|
||||
}
|
||||
@ -2233,18 +2236,28 @@ class ApiService {
|
||||
return response;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> fetchAgentUnusedCommissionList(id) async {
|
||||
Future<Map<String, dynamic>> fetchAgentUnusedCommissionList(
|
||||
id, {
|
||||
String? fromDate,
|
||||
String? toDate,
|
||||
}) async {
|
||||
print('fetchAGENTNameDropDown');
|
||||
if (_token == null) {
|
||||
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;
|
||||
print('fetchAGENTNameDropDown 1');
|
||||
url = Uri.parse(
|
||||
'${Env.apiUrl}invoice/getAgentUnusedCommissionList?manager_id=$id',
|
||||
);
|
||||
|
||||
'${Env.apiUrl}invoice/getAgentUnusedCommissionList',
|
||||
).replace(queryParameters: queryParameters);
|
||||
|
||||
print('fetchAGENTNameDropDown 2');
|
||||
final headers = {
|
||||
@ -2438,24 +2451,33 @@ class ApiService {
|
||||
Future<Map<String, dynamic>> getPayoutList({
|
||||
String? fromDate,
|
||||
String? toDate,
|
||||
String? agentName,
|
||||
String? agentCode,
|
||||
String? policyNo,
|
||||
}) async {
|
||||
// print(_token);
|
||||
if (_token == null) {
|
||||
await _initializeToken();
|
||||
}
|
||||
|
||||
final Uri url;
|
||||
if (fromDate != null &&
|
||||
fromDate.isNotEmpty &&
|
||||
toDate != null &&
|
||||
toDate.isNotEmpty) {
|
||||
url = Uri.parse('${Env.apiUrl}invoice/list').replace(
|
||||
queryParameters: {'from_date': fromDate, 'to_date': toDate},
|
||||
);
|
||||
} else {
|
||||
url = Uri.parse('${Env.apiUrl}invoice/list');
|
||||
final query = <String, String>{};
|
||||
void addParam(String key, String? value) {
|
||||
final trimmed = value?.trim();
|
||||
if (trimmed != null && trimmed.isNotEmpty) {
|
||||
query[key] = trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
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 = {
|
||||
'Authorization': 'Bearer $_token' ?? '',
|
||||
'app-signature': Env.App_Signature,
|
||||
|
||||
@ -99,7 +99,8 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
||||
if (route.contains('broker') ||
|
||||
route.contains('payment') ||
|
||||
route.contains('vehicletype') ||
|
||||
route.contains('endorsementtype')) {
|
||||
route.contains('endorsementtype') ||
|
||||
route.contains('insurer')) {
|
||||
return 'Masters';
|
||||
}
|
||||
if (route.contains('payoutgrid')) {
|
||||
@ -361,6 +362,15 @@ class _AppHeaderState extends ConsumerState<AppHeader> {
|
||||
context.go(AppRoutes.paymentModeLst);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
_buildPopupItem(
|
||||
label: "Insurer",
|
||||
onTap: () {
|
||||
setState(() => _activeMenu = 'Masters');
|
||||
_hidePopup();
|
||||
context.go(AppRoutes.insurerLst);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
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.
|
||||
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({
|
||||
super.key,
|
||||
this.showBrokerFilter = true,
|
||||
@ -55,6 +61,8 @@ class DateFilterRowPayout extends ConsumerStatefulWidget {
|
||||
this.isMobile = false,
|
||||
this.dataFrom,
|
||||
this.policyTableHasRows,
|
||||
this.tableSearchFields,
|
||||
this.passDateRangeToAgentList = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -115,7 +123,9 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
||||
|
||||
if (!widget.showBrokerFilter && managerId != null) {
|
||||
// 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 {
|
||||
if (_shouldDeferAgentListUntilDateRange()) {
|
||||
setState(() {
|
||||
getPartnerData = [];
|
||||
filteredPartnerData = [];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
print('getAgentListData called');
|
||||
setState(() {
|
||||
@ -163,7 +201,11 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await apiService.fetchAgentUnusedCommissionList(id);
|
||||
final response = await apiService.fetchAgentUnusedCommissionList(
|
||||
id,
|
||||
fromDate: _agentListFromDate(),
|
||||
toDate: _agentListToDate(),
|
||||
);
|
||||
print('getAgentListData called response');
|
||||
print('get Agent- ${response['data']}');
|
||||
if (response['status'] == 'success') {
|
||||
@ -276,7 +318,15 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
||||
child: Tooltip(
|
||||
message: 'Refresh',
|
||||
child: IconButton(
|
||||
onPressed: widget.onRefresh,
|
||||
onPressed: () {
|
||||
if (widget.passDateRangeToAgentList) {
|
||||
setState(() {
|
||||
getPartnerData = [];
|
||||
filteredPartnerData = [];
|
||||
});
|
||||
}
|
||||
widget.onRefresh();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
size: 18,
|
||||
@ -304,6 +354,10 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
||||
if (isMobile) {
|
||||
return [
|
||||
buildDateRangeFilter(context),
|
||||
if (widget.tableSearchFields != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
widget.tableSearchFields!,
|
||||
],
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: spacing,
|
||||
@ -315,6 +369,10 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
||||
}
|
||||
return [
|
||||
buildDateRangeFilter(context),
|
||||
if (widget.tableSearchFields != null) ...[
|
||||
SizedBox(width: spacing),
|
||||
widget.tableSearchFields!,
|
||||
],
|
||||
SizedBox(width: spacing),
|
||||
Spacer(),
|
||||
...partnerActionsRow,
|
||||
@ -349,7 +407,12 @@ class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
|
||||
txtheight: 32,
|
||||
lastDate: DateTime.now(),
|
||||
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/userRoleProvider.dart';
|
||||
import '../../themes/indicators/export_btn.dart';
|
||||
import '../../themes/indicators/search_field_theme.dart';
|
||||
import 'custom_dateRange.dart';
|
||||
|
||||
class PayOutDetails extends ConsumerStatefulWidget {
|
||||
@ -69,6 +70,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
'invoiceDate',
|
||||
'invoiceStatus',
|
||||
'utrNumber',
|
||||
'tableSearch',
|
||||
];
|
||||
|
||||
// --------------------------
|
||||
@ -218,43 +220,38 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void filterPolicyData(String query) {
|
||||
final lowerQuery = query.toLowerCase();
|
||||
String _partnerDisplayText(Map<String, dynamic> item) {
|
||||
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(() {
|
||||
currentPage = 1;
|
||||
if (query.trim().isEmpty) {
|
||||
print('PD => filteredPolicies2');
|
||||
filteredPolicies = List.from(masterPolicies);
|
||||
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();
|
||||
filteredPolicies = masterPolicies
|
||||
.where((item) => _matchesTableSearch(item, query))
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
|
||||
@ -269,6 +266,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
selectedPolicies = {};
|
||||
totalPolicies = '';
|
||||
totalCommission = '';
|
||||
masterPolicies = [];
|
||||
filteredPolicies = [];
|
||||
currentPage = 1;
|
||||
_disposeCommissionControllers();
|
||||
@ -407,21 +405,17 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
if (response['status'] == 'success') {
|
||||
print('PD =>API Data response - $response');
|
||||
setState(() {
|
||||
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
||||
_rebuildCommissionControllers(filteredPolicies);
|
||||
masterPolicies = List<Map<String, dynamic>>.from(response['data']);
|
||||
filteredPolicies = List.from(masterPolicies);
|
||||
_rebuildCommissionControllers(masterPolicies);
|
||||
hasFetchedTableData = true;
|
||||
currentPage = 1;
|
||||
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 {
|
||||
setState(() {
|
||||
masterPolicies = [];
|
||||
filteredPolicies = [];
|
||||
_rebuildCommissionControllers(filteredPolicies);
|
||||
hasFetchedTableData = true;
|
||||
@ -431,6 +425,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
} catch (e) {
|
||||
print('PD =>Exception occurred: $e');
|
||||
setState(() {
|
||||
masterPolicies = [];
|
||||
filteredPolicies = [];
|
||||
_rebuildCommissionControllers(filteredPolicies);
|
||||
hasFetchedTableData = false;
|
||||
@ -900,9 +895,13 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
role: roleId,
|
||||
id: userId,
|
||||
showBrokerFilter: false,
|
||||
passDateRangeToAgentList: true,
|
||||
policyTableHasRows: filteredPolicies.isNotEmpty,
|
||||
selectedParnter: selectedAgentId,
|
||||
selectedBroker: selectedBrokerID,
|
||||
tableSearchFields: hasFetchedTableData
|
||||
? _buildPolicyTableSearchFields(context)
|
||||
: null,
|
||||
|
||||
onPartnerChanges: (val) {
|
||||
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) {
|
||||
if (filteredPolicies.isEmpty) {
|
||||
return const SizedBox(
|
||||
@ -1205,6 +1222,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
Widget _buildDataRow(Map<String, dynamic> item, int sno) {
|
||||
final int id = int.tryParse(item["policy_id"]?.toString() ?? "0") ?? 0;
|
||||
final bool isSelected = selectedPolicies.contains(id);
|
||||
final partner = _partnerDisplayText(item);
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
@ -1274,9 +1292,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
Expanded(
|
||||
flex: _flexAgent,
|
||||
child: Text(
|
||||
(item["agent_code"] != null && item["agent_name"] != null)
|
||||
? '${item["agent_code"]} - ${item["agent_name"]}'
|
||||
: (item["agent_name"] ?? '-'),
|
||||
partner.isEmpty ? '-' : partner,
|
||||
textAlign: TextAlign.left,
|
||||
style: _tableDataStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@ -1345,9 +1361,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
'sno': 0,
|
||||
'policy_no': p['policy_no'],
|
||||
'policy_date': _formatDate((p['issued_date'] ?? '').toString()),
|
||||
'partner': (p["agent_code"] != null && p["agent_name"] != null)
|
||||
? '${p["agent_code"]} - ${p["agent_name"]}'
|
||||
: (p["agent_name"] ?? '-').toString(),
|
||||
'partner': _partnerDisplayText(p).isEmpty
|
||||
? '-'
|
||||
: _partnerDisplayText(p),
|
||||
'customer': p['customer_name'],
|
||||
'premium': _amountText(p['premium_amount']),
|
||||
'payout': _amountText(p['commission_amount']),
|
||||
|
||||
@ -24,6 +24,8 @@ class PayoutList extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
static const double _payoutFilterFieldHeight = 35;
|
||||
|
||||
int currentPage = 1;
|
||||
int itemsPerPage = 10;
|
||||
bool isLoading = false;
|
||||
@ -35,7 +37,13 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
late ApiService apiService;
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
Map<String, TextEditingController> controllers = {};
|
||||
List<String> tabHeader = ['startDate', 'endDate'];
|
||||
List<String> tabHeader = [
|
||||
'startDate',
|
||||
'endDate',
|
||||
'agentName',
|
||||
'agentCode',
|
||||
'policyNo',
|
||||
];
|
||||
dynamic SelectedStatus;
|
||||
dynamic SelectedStaffId;
|
||||
DateTime _paymentDate = DateTime.now();
|
||||
@ -75,6 +83,9 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
final response = await apiService.getPayoutList(
|
||||
fromDate: from.isNotEmpty ? from : null,
|
||||
toDate: to.isNotEmpty ? to : null,
|
||||
agentName: controllers['agentName']?.text,
|
||||
agentCode: controllers['agentCode']?.text,
|
||||
policyNo: controllers['policyNo']?.text,
|
||||
);
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
@ -157,13 +168,11 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
|
||||
void refrshfilterDateRange() {
|
||||
setState(() {
|
||||
// SelectedStaffId = null;
|
||||
controllers['startDate']!.clear();
|
||||
controllers['endDate']!.clear();
|
||||
|
||||
controllers['startDate']?.text = '';
|
||||
controllers['endDate']?.text = '';
|
||||
// Reset the FormField validation
|
||||
controllers['agentName']!.clear();
|
||||
controllers['agentCode']!.clear();
|
||||
controllers['policyNo']!.clear();
|
||||
_formKey.currentState?.reset();
|
||||
});
|
||||
getPayoutList();
|
||||
@ -174,13 +183,16 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
final q = query.toLowerCase();
|
||||
setState(() {
|
||||
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'])
|
||||
.toLowerCase();
|
||||
final statusSearch =
|
||||
_exportPayoutStatusLabel(item['payout_status']).toLowerCase();
|
||||
|
||||
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_amount_indian_format'] ?? '-')
|
||||
.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) {
|
||||
return DateFilterRow(
|
||||
final isMobile = ResponsiveLayout.isMobile(context);
|
||||
final dateFilter = DateFilterRow(
|
||||
compactDateFiltersOnly: true,
|
||||
hideFilterActions: true,
|
||||
dataFrom: 'Payout',
|
||||
role: roleId,
|
||||
id: userId,
|
||||
@ -665,10 +790,31 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
endController: controllers['endDate']!,
|
||||
onStatusChanged: (_) {},
|
||||
formKey: _formKey,
|
||||
isMobile: ResponsiveLayout.isMobile(context),
|
||||
isMobile: isMobile,
|
||||
onFilter: filterDateRange,
|
||||
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) {
|
||||
|
||||
@ -169,6 +169,8 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
|
||||
// int selectedIndex = 1;
|
||||
int selectedIndex = 0;
|
||||
int autoRefreshIndex = 0;
|
||||
int _staffListRequestSeq = 0;
|
||||
int _staffListInFlight = 0;
|
||||
|
||||
final List<String> enquiryTabs = ["In Progress", "Completed"];
|
||||
|
||||
@ -452,7 +454,6 @@ if (managerId != null &&
|
||||
}
|
||||
|
||||
void filterDateRange() {
|
||||
// ✅ Call your API
|
||||
_searchStaffController.clear();
|
||||
getStaffList(
|
||||
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
|
||||
void dispose() {
|
||||
refreshSub.close();
|
||||
@ -520,31 +557,19 @@ if (managerId != null &&
|
||||
List<Map<String, dynamic>> get paginatedCompletedData {
|
||||
final dataToUse = filteredData;
|
||||
|
||||
if (dataToUse.isEmpty) return [];
|
||||
if (dataToUse.isEmpty || selectedIndex != 1) return dataToUse;
|
||||
|
||||
if (selectedIndex == 1) {
|
||||
totalCompletedPages = (dataToUse.length / completedItemsPerPage)
|
||||
.ceil()
|
||||
.clamp(1, double.infinity)
|
||||
.toInt();
|
||||
final maxPage = (dataToUse.length / completedItemsPerPage)
|
||||
.ceil()
|
||||
.clamp(1, 999999);
|
||||
final safePage = completedCurrentPage.clamp(1, maxPage);
|
||||
final startIndex = (safePage - 1) * completedItemsPerPage;
|
||||
final endIndex = (startIndex + completedItemsPerPage).clamp(
|
||||
0,
|
||||
dataToUse.length,
|
||||
);
|
||||
|
||||
final startIndex = (completedCurrentPage - 1) * completedItemsPerPage;
|
||||
|
||||
// 🚨 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;
|
||||
return dataToUse.sublist(startIndex, endIndex);
|
||||
}
|
||||
|
||||
Future<void> autoRefrshfilterDateRange() async {
|
||||
@ -706,6 +731,10 @@ if (managerId != null &&
|
||||
}
|
||||
}
|
||||
|
||||
void _syncStaffListLoader() {
|
||||
isLoadingStaffList = isActionable && _staffListInFlight > 0;
|
||||
}
|
||||
|
||||
Future<void> getStaffList(int managerId,
|
||||
role, {
|
||||
String fromDate = '',
|
||||
@ -714,8 +743,10 @@ if (managerId != null &&
|
||||
String SelectedStaffId = '',
|
||||
}) async {
|
||||
print('A613 => Fns called => $managerId | $role');
|
||||
final requestId = ++_staffListRequestSeq;
|
||||
setState(() {
|
||||
isLoadingStaffList = true;
|
||||
_staffListInFlight++;
|
||||
_syncStaffListLoader();
|
||||
});
|
||||
print('A713 => Fns called => $managerId | $role');
|
||||
|
||||
@ -818,6 +849,11 @@ if (managerId != null &&
|
||||
print('ToDate : $toDate');
|
||||
print('A1213 => Fns called => $managerId | $role');
|
||||
|
||||
if (requestId != _staffListRequestSeq) {
|
||||
print('Ignoring stale enquiry list response: $requestId');
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_resetKeyEnable = false;
|
||||
|
||||
@ -827,36 +863,30 @@ if (managerId != null &&
|
||||
if (data is List) {
|
||||
getStaffData = List<Map<String, dynamic>>.from(data);
|
||||
originalData = List<Map<String, dynamic>>.from(data);
|
||||
|
||||
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);
|
||||
_splitEnquiryLists(originalData);
|
||||
|
||||
if (SelectedStatus == 'Completed') {
|
||||
selectedIndex = 1;
|
||||
}
|
||||
|
||||
_syncFilteredDataForCurrentTab(resetCompletedPage: true);
|
||||
|
||||
print('filteredData count: ${filteredData.length}');
|
||||
print('inProgressData count: ${inProgressData.length}');
|
||||
print('completedData count: ${completedData.length}');
|
||||
} else if (data is Map) {
|
||||
getStaffData = [Map<String, dynamic>.from(data)];
|
||||
originalData = List.from(getStaffData);
|
||||
inProgressData = [];
|
||||
completedData = [];
|
||||
filteredData = [];
|
||||
} else {
|
||||
getStaffData = [];
|
||||
originalData = [];
|
||||
inProgressData = [];
|
||||
completedData = [];
|
||||
filteredData = [];
|
||||
}
|
||||
originalData = getStaffData;
|
||||
filteredData = List.from(originalData);
|
||||
});
|
||||
|
||||
// ✅ FIX CHANGE 2: Clear dashboardKey AFTER first successful load
|
||||
@ -868,15 +898,22 @@ if (managerId != null &&
|
||||
print('✅ dashboardKey cleared after first successful load');
|
||||
}
|
||||
|
||||
} else {
|
||||
getStaffData = [];
|
||||
originalData = [];
|
||||
} else if (requestId == _staffListRequestSeq) {
|
||||
setState(() {
|
||||
getStaffData = [];
|
||||
originalData = [];
|
||||
inProgressData = [];
|
||||
completedData = [];
|
||||
filteredData = [];
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print('Exception occurred: $e');
|
||||
} finally {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
isLoadingStaffList = false;
|
||||
if (_staffListInFlight > 0) _staffListInFlight--;
|
||||
_syncStaffListLoader();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1913,7 +1950,9 @@ if (managerId != null &&
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
final isDesktop = !ResponsiveLayout.isMobile(context);
|
||||
return Column(
|
||||
return Stack(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 10),
|
||||
@ -2005,6 +2044,7 @@ if (managerId != null &&
|
||||
isActionable = newValue;
|
||||
userPreference =
|
||||
newValue; // Save this as the new "normal"
|
||||
_syncStaffListLoader();
|
||||
});
|
||||
final prefs =
|
||||
await SharedPreferences.getInstance();
|
||||
@ -2104,6 +2144,7 @@ if (managerId != null &&
|
||||
// CASE 2: Search cleared -> Restore the status from memory
|
||||
isActionable = userPreference;
|
||||
}
|
||||
_syncStaffListLoader();
|
||||
});
|
||||
},
|
||||
),
|
||||
@ -2167,6 +2208,19 @@ if (managerId != null &&
|
||||
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,
|
||||
'Assigned': true,
|
||||
'In progress': true,
|
||||
'Completed': true,
|
||||
};
|
||||
|
||||
Map<String, String> statusGroupMap = {
|
||||
@ -2211,17 +2266,17 @@ if (managerId != null &&
|
||||
grouped["To be assigned"] = [];
|
||||
grouped["Assigned"] = [];
|
||||
grouped["In progress"] = [];
|
||||
} else {
|
||||
grouped["Completed"] = [];
|
||||
}
|
||||
|
||||
for (var item in filteredDataByTab) {
|
||||
final status = (item['enquiry_status'] ?? '').toString();
|
||||
if (grouped.containsKey(status)) {
|
||||
grouped[status]!.add(item);
|
||||
for (final item in filteredDataByTab) {
|
||||
final status = (item['enquiry_status'] ?? '').toString();
|
||||
if (grouped.containsKey(status)) {
|
||||
grouped[status]!.add(item);
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
grouped["Completed"] = List<Map<String, dynamic>>.from(filteredDataByTab);
|
||||
return grouped;
|
||||
}
|
||||
|
||||
@ -2369,7 +2424,8 @@ if (managerId != null &&
|
||||
// Replace your _buildDataTableContent() method with this optimized version
|
||||
|
||||
Widget _buildDataTableContent() {
|
||||
if (filteredData.isEmpty) {
|
||||
final visibleRows = filteredDataByTab;
|
||||
if (visibleRows.isEmpty) {
|
||||
return const SizedBox(
|
||||
height: 50,
|
||||
child: Center(child: Text('No available data')),
|
||||
@ -3485,16 +3541,8 @@ if (managerId != null &&
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
print('TAPPED TAB');
|
||||
selectedIndex = index;
|
||||
|
||||
filteredData = (index == 0) ? inProgressData : completedData;
|
||||
if (index == 1) {
|
||||
completedCurrentPage = 1;
|
||||
// final newValue = false;
|
||||
// isActionable = newValue;
|
||||
}
|
||||
|
||||
_syncFilteredDataForCurrentTab(resetCompletedPage: index == 1);
|
||||
_searchStaffController.clear();
|
||||
});
|
||||
},
|
||||
|
||||
@ -25,6 +25,7 @@ class ThemedSearchField extends HookWidget {
|
||||
this.maxLength, // ✅ new
|
||||
this.inputFormatters,
|
||||
this.onChanged,
|
||||
this.showSearchIcon = true,
|
||||
});
|
||||
|
||||
final String hintText;
|
||||
@ -44,6 +45,7 @@ class ThemedSearchField extends HookWidget {
|
||||
final TextInputType? keyboardType;
|
||||
final int? maxLength;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final bool showSearchIcon;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -86,11 +88,16 @@ class ThemedSearchField extends HookWidget {
|
||||
// maxWidth: 25 + 16 + 10,
|
||||
// maxHeight: 25 + (8 * 2),
|
||||
// ),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 16, end: 10),
|
||||
child: Icon(Icons.search, color: const Color(0xFF94A3B8), size: 18),
|
||||
// child: Icon(Icons.search, color: const Color(0xFF686868), size: 20),
|
||||
),
|
||||
suffixIcon: showSearchIcon
|
||||
? Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: 16, end: 10),
|
||||
child: Icon(
|
||||
Icons.search,
|
||||
color: const Color(0xFF94A3B8),
|
||||
size: 18,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
return Container(
|
||||
|
||||
@ -58,6 +58,8 @@ class DateFilterRow extends ConsumerStatefulWidget {
|
||||
/// When true, only start/end date fields and filter/refresh actions are shown
|
||||
/// (no status, staff, insurer, or partner controls).
|
||||
final bool compactDateFiltersOnly;
|
||||
/// When true, filter/refresh buttons are omitted (caller renders them elsewhere).
|
||||
final bool hideFilterActions;
|
||||
final role;
|
||||
final id;
|
||||
|
||||
@ -82,6 +84,7 @@ class DateFilterRow extends ConsumerStatefulWidget {
|
||||
required this.id,
|
||||
this.isMobile = false,
|
||||
this.compactDateFiltersOnly = false,
|
||||
this.hideFilterActions = false,
|
||||
this.dataFrom,
|
||||
});
|
||||
|
||||
@ -344,7 +347,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
||||
];
|
||||
|
||||
if (isMobile) {
|
||||
// For mobile: buttons below fields
|
||||
if (widget.hideFilterActions) return dateFields;
|
||||
return [
|
||||
...dateFields,
|
||||
SizedBox(height: 10),
|
||||
@ -352,6 +355,7 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
||||
];
|
||||
} else {
|
||||
// For desktop: buttons inline with fields
|
||||
if (widget.hideFilterActions) return dateFields;
|
||||
return [...dateFields, ...buttons];
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,8 +16,9 @@
|
||||
-->
|
||||
<!-- <base href="$FLUTTER_BASE_HREF"> -->
|
||||
<base href="/partner/">
|
||||
<!-- <base href="/nhance/partner/app/">-->
|
||||
<!-- <base href="{Env.baseHref}">-->
|
||||
<!-- below one is dev-->
|
||||
<!-- <base href="/nhance/partner/app/">-->
|
||||
<!-- <base href="{Env.baseHref}">-->
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user