nhance_partner/lib/presentation/screens/payout/custom_dateRange.dart

913 lines
31 KiB
Dart

import 'dart:math' show min;
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:nhance_partner/data/utils/toastNotification.dart';
import '../../../core/services/api_service.dart';
import '../../layouts/responsive_layout.dart';
import '../../providers/manager_provider.dart';
import '../../providers/userRoleProvider.dart';
import '../../themes/indicators/date_range_picker_field.dart';
import '../../themes/indicators/input_field_decoration.dart';
class DateFilterRowPayout extends ConsumerStatefulWidget {
final bool showBrokerFilter;
final TextEditingController startController;
final String? selectedBroker;
final List<dynamic>? selectedParnter;
final String? selectedStaffId;
final TextEditingController endController;
final ValueChanged<String?>? onBrokerChanged;
final ValueChanged<List<String>>? onPartnerChanges;
final String? dataFrom;
final VoidCallback onFilter;
final VoidCallback onRefresh;
final GlobalKey<FormState> formKey;
final bool isMobile;
final role;
final id;
/// When non-null, toggles filter layout for payout details:
/// - `false` — no table rows: show date range only (hide partner + actions).
/// - `true` — table has rows: show partner (+ broker if [showBrokerFilter]) and
/// search/reset aligned to the right; hide date range.
/// 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,
required this.startController,
required this.endController,
required this.onFilter,
required this.onRefresh,
this.onBrokerChanged,
this.onPartnerChanges,
required this.selectedStaffId,
required this.selectedParnter,
this.selectedBroker,
required this.formKey,
required this.role,
required this.id,
this.isMobile = false,
this.dataFrom,
this.policyTableHasRows,
this.tableSearchFields,
this.passDateRangeToAgentList = false,
});
@override
ConsumerState<DateFilterRowPayout> createState() => _DateFilterRowState();
}
class _DateFilterRowState extends ConsumerState<DateFilterRowPayout> {
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKey =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>> dropDownKeyBroker =
GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
final GlobalKey<DropdownSearchState<Map<String, dynamic>>>
dropDownKeyPartner = GlobalKey<DropdownSearchState<Map<String, dynamic>>>();
late ApiService apiService;
// List<dynamic>? selectedPartnerIds = [];
List<Map<String, dynamic>> getPartnerData = [];
List<Map<String, dynamic>> filteredPartnerData = [];
List<Map<String, dynamic>> getBrokerData = [];
List<Map<String, dynamic>> filteredBrokerData = [];
String? selectedStaff;
String? selectedBroker;
String? selectedPartner;
String? selectedStaffName;
dynamic role;
dynamic managerId;
bool isLoading = false;
bool isLoadingBroker = false;
bool isLoadingAgentList = false;
@override
void initState() {
super.initState();
apiService = ApiService();
Future.microtask(() {
managerId = ref.watch(managerIdProvider);
final userID = ref.watch(userIdProvider);
role = ref.watch(userRoleProvider);
print("managerId - $managerId");
// if (userID != null && role != null) {
// print('hansles');
// getPartnerDetails(userID);
// }
if (widget.showBrokerFilter &&
managerId != null &&
widget.selectedBroker != null) {
print('managerId - $managerId');
print('SelectedBroker - $widget.selectedBroker');
getAgentList(managerId);
}
if (!widget.showBrokerFilter && managerId != null) {
// Broker filter hidden: load partner list directly by manager.
if (!_shouldDeferAgentListUntilDateRange()) {
getAgentList(managerId);
}
}
});
if (widget.showBrokerFilter) {
getBroker();
}
}
Future<void> getBroker() async {
print('getBroker called');
setState(() {
isLoadingBroker = true;
});
try {
final response = await apiService.fetchMasterDropDown('Broker');
if (response['status'] == 200) {
print('getBroker - ${response['data']}');
setState(() {
getBrokerData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getBrokerData');
filteredBrokerData = List.from(getBrokerData);
print('originalData - $filteredBrokerData');
});
} else {
getBrokerData = [];
filteredBrokerData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoadingBroker = false;
});
}
}
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(() {
isLoadingAgentList = true;
});
try {
final response = await apiService.fetchAgentUnusedCommissionList(
id,
fromDate: _agentListFromDate(),
toDate: _agentListToDate(),
);
print('getAgentListData called response');
print('get Agent- ${response['data']}');
if (response['status'] == 'success') {
print('get Agent- ${response['data']}');
setState(() {
// 1. Convert the response to a list
final rawList = List<Map<String, dynamic>>.from(response['data']);
print('API Data - rawList');
// 2. Filter out items where agent_id is null or empty
getPartnerData = rawList.where((item) {
final id = item['agent_id'];
return id != null && id.toString().isNotEmpty;
}).toList();
print('Filtered API Data (No Null IDs) - $getPartnerData');
// 3. Sync the filtered data to your display list
filteredPartnerData = List.from(getPartnerData);
});
} else {
getPartnerData = [];
filteredPartnerData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoadingAgentList = false;
});
}
}
// Future<void> getPartnerDetails(int id) async {
// print('getPartnerDetails called By handler');
// setState(() {
// isLoading = true;
// });
//
// try {
// // final response = await apiService.fetchStaffUserList(id, role);
// final response = await apiService.fetchStaffListForEnquiryAssignDropDown(
// managerId,
// id,
// role,
// );
// if (response['status'] == 'success') {
// print('getStaffDetails - ${response['data']}');
// setState(() {
// getPartnerData = List<Map<String, dynamic>>.from(response['data']);
// print('API Data - $getPartnerData');
//
// filteredPartnerData = List.from(getPartnerData);
// print('originalData - $filteredPartnerData');
// });
// } else {
// getPartnerData = [];
// filteredPartnerData = [];
// }
// } catch (e) {
// print('Exception occurred: $e');
// } finally {
// setState(() {
// isLoading = false;
// });
// }
// }
@override
Widget build(BuildContext context) {
final spacing = 5.0;
List<Widget> getRowChildren(bool isMobile, double spacing) {
final hasDateRange = widget.startController.text.trim().isNotEmpty &&
widget.endController.text.trim().isNotEmpty;
double partnerMaxWidth() {
final sw = MediaQuery.sizeOf(context).width;
return min(sw * 0.32, 420);
}
final buttons = [
Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0),
child: Tooltip(
message: 'Filter',
child: IconButton(
icon: const Icon(
Icons.search_rounded,
size: 18,
color: const Color(0xFF94A3B8),
),
onPressed: () {
if (widget.formKey.currentState?.validate() != true) return;
final partners = widget.selectedParnter;
final hasPartner = partners != null &&
partners.isNotEmpty &&
partners.any((e) => e.toString().trim().isNotEmpty);
if (!hasPartner) {
ToastHelper.showWarningToast(
context,
'Please select a partner before searching.',
);
return;
}
widget.onFilter();
},
),
),
),
Padding(
padding: const EdgeInsets.all(0.0),
child: Tooltip(
message: 'Refresh',
child: IconButton(
onPressed: () {
if (widget.passDateRangeToAgentList) {
setState(() {
getPartnerData = [];
filteredPartnerData = [];
});
}
widget.onRefresh();
},
icon: const Icon(
Icons.refresh,
size: 18,
color: const Color(0xFF94A3B8),
),
),
),
),
];
final partnerActionsRow = <Widget>[
if (widget.showBrokerFilter) ...[
buildBroker(context),
SizedBox(width: spacing),
],
buildPartner(context, maxWidth: partnerMaxWidth()),
SizedBox(width: spacing),
...buttons,
];
if (!hasDateRange) {
return [buildDateRangeFilter(context)];
}
if (isMobile) {
return [
buildDateRangeFilter(context),
if (widget.tableSearchFields != null) ...[
const SizedBox(height: 10),
widget.tableSearchFields!,
],
const SizedBox(height: 10),
Wrap(
spacing: spacing,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.end,
children: partnerActionsRow,
),
];
}
return [
buildDateRangeFilter(context),
if (widget.tableSearchFields != null) ...[
SizedBox(width: spacing),
widget.tableSearchFields!,
],
SizedBox(width: spacing),
Spacer(),
...partnerActionsRow,
];
}
return Form(
key: widget.formKey,
child: widget.isMobile
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: getRowChildren(widget.isMobile, spacing),
)
: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: getRowChildren(widget.isMobile, spacing),
),
);
}
Widget buildDateRangeFilter(BuildContext context) {
final w = ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.15;
return DateRangePickerField(
startController: widget.startController,
endController: widget.endController,
hintText: 'Select date range',
txtwidth: w,
txtheight: 32,
lastDate: DateTime.now(),
rangeValidator: DateRangePickerField.defaultFilterValidator,
onRangeSelected: (_) => Future.microtask(() {
if (widget.passDateRangeToAgentList && managerId != null) {
getAgentList(managerId);
}
widget.onFilter();
}),
);
}
Widget buildBroker(BuildContext context) {
Map<String, dynamic>? selectedBrokers = filteredBrokerData.firstWhere(
(item) => item['id'].toString() == widget.selectedBroker,
orElse: () => {},
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Broker *', style: _textStyle),
SizedBox(height: 5),
SizedBox(
height: 35,
width: MediaQuery.of(context).size.width * 0.15,
child: DropdownSearch<Map<String, dynamic>>(
key: dropDownKeyBroker,
selectedItem: selectedBrokers.isNotEmpty ? selectedBrokers : null,
items: (filter, infiniteScrollProps) {
return filteredBrokerData;
},
itemAsString: (val) => val['name'].toString(), // what to show
compareFn: (item, selectedItem) =>
item['id'] == selectedItem['id'], // ✅ compare by id
// validator: (val) {
// if (val == null) {
// return "Required"; // ✅ error message
// }
// return null;
// },
suffixProps: DropdownSuffixProps(
// make sure the dropdown button is visible
dropdownButtonProps: DropdownButtonProps(
isVisible: true,
padding: EdgeInsets.zero, // remove default padding
constraints: const BoxConstraints(
// shrink icon tap area
minWidth: 12,
minHeight: 12,
),
iconSize: 15, // smaller icon
// icon: const Icon(Icons.arrow_drop_down),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem != null ? selectedItem['name'].toString() : "",
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.black,
// color: Color(0XFF6366F1),
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: false,
),
),
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "Broker",
).copyWith(
filled: true,
fillColor:
Colors.white, // 👈 makes the dropdown input white
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.1,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: const BorderSide(
color: Color(0xFFE2E8F0),
width: 0.5,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: MenuProps(
backgroundColor:
Colors.white, // 👈 sets dropdown background to white
),
showSearchBox: true,
searchFieldProps: TextFieldProps(
autofocus: true,
style: GoogleFonts.inter(fontSize: 11, color: Colors.black),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(1),
filled: true,
fillColor: Colors.white,
hintText: "Search Broker...",
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
item['name'].toString(),
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
// constraints: BoxConstraints(),
),
// onChanged: (val) {
// if (val != null) {
// print("Selected Broker : ${val['name']}");
// print("Id: ${val['id']}");
// // selectedBroker = val['id'];
// widget.onBrokerChanged?.call(val['id'].toString());
// // widget.onBrokerChanged?.call(val['id']);
// // controllers['agentId']?.text = val['agent_code'];
// // agentId = agent['id'];
// }
// },
onChanged: (val) {
if (val != null) {
String selectedId = val['id'].toString();
// 1. Notify parent of the change
widget.onBrokerChanged?.call(selectedId);
// 2. Clear current partner list so user doesn't see old data
setState(() {
filteredPartnerData = [];
});
// 3. Fetch new agents based on this broker
getAgentList(managerId!);
}
},
),
),
],
);
}
Widget buildPartner(BuildContext context, {double? maxWidth}) {
final w = maxWidth ?? MediaQuery.of(context).size.width * 0.4;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Partner", style: _textStyle),
SizedBox(height: 5),
SizedBox(
height: 35,
width: w,
child: DropdownSearch<Map<String, dynamic>>.multiSelection(
key: dropDownKeyPartner,
// selectedItem: selectedHandlered.isNotEmpty
// ? selectedHandlered
// : null,
selectedItems: filteredPartnerData
.where(
(item) => (widget.selectedParnter ?? []).contains(item['agent_id']),
)
.toList(),
// selectedItems: filteredPartnerData
// .where(
// (item) => (selectedPartnerIds ?? []).contains(item['id']),
// )
// .toList(),
items: (filter, infiniteScrollProps) {
return filteredPartnerData;
},
itemAsString: (val) => // what to show
"${val['agent_name']} | ${val['unused_commission_amount']} (${val['total_policies']})",
compareFn: (item, selectedItem) =>
item['agent_id'] == selectedItem['agent_id'], // ✅ compare by id
dropdownBuilder: (context, selectedItems) {
final safeItems = selectedItems;
final text = safeItems.isEmpty
? 'Select Partner'
: safeItems.length == 1
? "${safeItems.first['agent_name']} | ${safeItems.first['unused_commission_amount']} (${safeItems.first['total_policies']})"
: '${safeItems.length} partners selected';
return Align(
alignment: Alignment.centerLeft,
child: Text(
text,
style: GoogleFonts.inter(
fontSize: 12,
color: safeItems.isEmpty
? const Color(0xFF64748B)
: Colors.black,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
},
// validator: (val) {
// if (val == null) {
// return "Required"; // ✅ error message
// }
// return null;
// },
// validator: (val) {
// if (val == null || val.isEmpty) {
// return ""; // 👈 triggers error border, no text
// }
// return null;
//
// // if (val == null || val.isEmpty) {
// // return "Required";
// // }
// // return null;
// },
decoratorProps: DropDownDecoratorProps(
decoration:
AppInputDecorations.dropdownDecoration(
label: "",
).copyWith(
hintText: null,
hintStyle: GoogleFonts.inter(
fontSize: 12,
color: Colors.black,
),
filled: true,
errorStyle: const TextStyle(height: 0, fontSize: 0),
fillColor:
Colors.white, // 👈 makes the dropdown input white
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
), // 👈 Normal border
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xFFE2E8F0),
// color: Colors.white,
width: 1.5,
), // 👈 Focused border
),
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
),
),
),
popupProps: PopupPropsMultiSelection.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
showSearchBox: true,
menuProps: MenuProps(backgroundColor: Colors.white),
searchFieldProps: TextFieldProps(
autofocus: true,
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
hintText: "Search Partner...",
hintStyle: GoogleFonts.inter(
fontSize: 11,
color: Colors.black,
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
// color: Colors.blue,
color: Color(0xFFEDF6F5),
width: 1.5,
),
),
),
),
itemBuilder: (context, item, isDisabled, isSelected) {
return Container(
// color: isSelected ? Colors.blue.withOpacity(0.1) : null,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
child: Text(
"${item['agent_name']} | ${item['unused_commission_amount']} (${item['total_policies']})",
style: GoogleFonts.inter(fontSize: 12, color: Colors.black),
),
);
},
),
onChanged: (List<Map<String, dynamic>> selectedVals) {
// selectedPartnerIds = selectedVals
// .map((v) => v['id'].toString())
// .toList();
final ids = selectedVals.map((v) => v['agent_id'].toString()).toList();
print("Selected PArnter IDs: $widget.selectedPartnerIds");
widget.onPartnerChanges?.call(ids);
// widget.onPartnerChanges!(v['id']);
},
),
),
],
);
}
// Widget buildSelectStaffMem(BuildContext context) {
// Map<String, dynamic>? selectedVehicle;
// if (widget.selectedStaffId == null) {
// selectedVehicle = null;
// } else if (widget.selectedStaffId != null) {
// selectedVehicle = filteredPartnerData.firstWhere(
// (item) => item['id'] == widget.selectedStaffId,
// orElse: () => {}, // empty map
// );
// if (selectedVehicle.isEmpty) selectedVehicle = null;
// } else if (selectedStaff != null) {
// selectedVehicle = filteredPartnerData.firstWhere(
// (item) => item['id'] == selectedStaff,
// orElse: () => {}, // empty map
// );
// if (selectedVehicle.isEmpty) selectedVehicle = null;
// } else {
// selectedVehicle = null;
// }
// return Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // Text("Staff Name", style: _textStyle),
// // SizedBox(height: 10),
// SizedBox(
// height: 35,
// child: Container(
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(5.0),
// border: Border.all(color: Color(0xFFE2E8F0)),
// // color: Colors.black,
// ),
//
// width: ResponsiveLayout.isMobile(context)
// ? null
// : MediaQuery.of(context).size.width * 0.13,
// // height: 45,
// child: DropdownSearch<Map<String, dynamic>>(
// // key: dropDownKey,
// key: ValueKey(selectedStaff),
// // selectedItem: selectedVehicle.isNotEmpty ? selectedVehicle : null,
// selectedItem: selectedVehicle,
//
// items: (filter, infiniteScrollProps) {
// return filteredPartnerData;
// },
//
// itemAsString: (val) => val['name'].toString(),
// compareFn: (item, selectedItem) =>
// item['id'] == selectedItem['id'], // ✅ compare by id
// dropdownBuilder: (context, selectedItem) => Align(
// alignment: Alignment.centerLeft,
// child: Text(
// selectedItem != null ? selectedItem['name'].toString() : "",
// style: GoogleFonts.poppins(fontSize: 11, color: Colors.black),
// overflow: TextOverflow.ellipsis,
// maxLines: 1,
// softWrap: false,
// ),
// ),
// decoratorProps: DropDownDecoratorProps(
// decoration:
// AppInputDecorations.dropdownDecoration(
// label: "Select Staff ",
// ).copyWith(
// filled: true,
// fillColor:
// Colors.white, // 👈 makes the dropdown input white
// contentPadding: const EdgeInsets.symmetric(
// horizontal: 6,
// vertical: 1, // 👈 adjust this to make the field shorter
// ),
// ),
// ),
//
// popupProps: PopupProps.menu(
// fit: FlexFit.loose,
// constraints: BoxConstraints(maxHeight: 250),
// menuProps: MenuProps(
// backgroundColor:
// Colors.white, // 👈 sets dropdown background to white
// ),
// showSearchBox: true,
// searchFieldProps: TextFieldProps(
// decoration: InputDecoration(
// filled: true,
// fillColor: Colors.white,
// hintText: "Search Staff ...",
// hintStyle: GoogleFonts.inter(
// fontSize: 12,
// color: Colors.black,
// ),
// enabledBorder: OutlineInputBorder(
// borderSide: BorderSide(
// color: Colors.white,
// ), // 👈 Normal border
// ),
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(
// color: Colors.white,
// width: 1.5,
// ), // 👈 Focused border
// ),
// ),
// ),
//
// itemBuilder: (context, item, isDisabled, isSelected) {
// return Container(
// // color: isSelected ? Colors.blue.withOpacity(0.1) : null,
// padding: const EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 3,
// ),
// child: Text(
// item['name'].toString(),
// style: GoogleFonts.inter(
// fontSize: 13,
// color: Colors.black,
// ),
// ),
// );
// },
//
// // constraints: BoxConstraints(),
// ),
//
// onChanged: (val) {
// if (val != null) {
// print("Selected Staff : ${val['name']}");
// print("Id: ${val['id']}");
// // selectedStaffName = val['name'];
// // selectedStaff = val['id'];
// widget.onBrokerChanged!(val['id']);
// // if (widget.onFilterStaff != null)
// // widget.onFilterStaff!(val['id']);
//
// // Future.microtask(() => widget.onFilter());
// // controllers['agentId']?.text = val['agent_code'];
// // agentId = agent['id'];
// }
// },
// ),
// ),
// ),
// ],
// );
// }
static final _textStyle = GoogleFonts.poppins(
fontSize: 11,
fontWeight: FontWeight.w500,
);
}