insurer master ans claims

This commit is contained in:
Surendiran 2026-06-11 09:24:04 +05:30
parent 78d4d786a7
commit 6c65a71d1f
15 changed files with 1074 additions and 152 deletions

File diff suppressed because one or more lines are too long

View File

@ -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(

View File

@ -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(),

View File

@ -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';

View File

@ -735,6 +735,9 @@ 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,23 +2451,32 @@ 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' ?? '',

View File

@ -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') ...[

View 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,
);
}

View 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),
);
}

View File

@ -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,8 +123,10 @@ 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.
if (!_shouldDeferAgentListUntilDateRange()) {
getAgentList(managerId); getAgentList(managerId);
} }
}
}); });
if (widget.showBrokerFilter) { if (widget.showBrokerFilter) {
@ -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();
}),
); );
} }

View File

@ -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']),

View File

@ -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) {

View File

@ -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,22 +557,13 @@ 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, double.infinity) .clamp(1, 999999);
.toInt(); final safePage = completedCurrentPage.clamp(1, maxPage);
final startIndex = (safePage - 1) * completedItemsPerPage;
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( final endIndex = (startIndex + completedItemsPerPage).clamp(
0, 0,
dataToUse.length, dataToUse.length,
@ -544,9 +572,6 @@ if (managerId != null &&
return dataToUse.sublist(startIndex, endIndex); return dataToUse.sublist(startIndex, endIndex);
} }
return dataToUse;
}
Future<void> autoRefrshfilterDateRange() async { Future<void> autoRefrshfilterDateRange() async {
print('autorefrshfilterDateRange'); print('autorefrshfilterDateRange');
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@ -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) {
setState(() {
getStaffData = []; getStaffData = [];
originalData = []; 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();
}); });
}, },

View File

@ -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(
padding: const EdgeInsetsDirectional.only(start: 16, end: 10), padding: const EdgeInsetsDirectional.only(start: 16, end: 10),
child: Icon(Icons.search, color: const Color(0xFF94A3B8), size: 18), child: Icon(
// child: Icon(Icons.search, color: const Color(0xFF686868), size: 20), Icons.search,
color: const Color(0xFF94A3B8),
size: 18,
), ),
)
: null,
); );
return Container( return Container(

View File

@ -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];
} }
} }

View File

@ -16,6 +16,7 @@
--> -->
<!-- <base href="$FLUTTER_BASE_HREF"> --> <!-- <base href="$FLUTTER_BASE_HREF"> -->
<base href="/partner/"> <base href="/partner/">
<!-- below one is dev-->
<!-- <base href="/nhance/partner/app/">--> <!-- <base href="/nhance/partner/app/">-->
<!-- <base href="{Env.baseHref}">--> <!-- <base href="{Env.baseHref}">-->