nhance_partner/lib/presentation/screens/payout/payout_details_view.dart
2026-04-07 12:05:12 +05:30

570 lines
20 KiB
Dart

import 'package:flutter/gestures.dart';
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:intl/intl.dart';
import '../../../data/utils/Pagination.dart';
import '../../../core/routing/routes.dart';
import '../../../core/services/api_service.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';
/// Read-only payout invoice + policy lines (opened from payout list “View”).
class PayOutDetailsView extends ConsumerStatefulWidget {
final Map<String, dynamic> viewItem;
const PayOutDetailsView({super.key, required this.viewItem});
@override
ConsumerState<PayOutDetailsView> createState() => _PayOutDetailsViewState();
}
class _PayOutDetailsViewState extends ConsumerState<PayOutDetailsView> {
final TextEditingController _searchStaffController = TextEditingController();
List<Map<String, dynamic>> masterPolicies = [];
List<Map<String, dynamic>> filteredPolicies = [];
bool isLoading = false;
late ApiService apiService;
dynamic managerId;
dynamic totalPolicies = '0';
dynamic totalCommission = '0';
int _rowsPerPage = 10;
int _currentPage = 1;
@override
void initState() {
super.initState();
apiService = ApiService();
Future.microtask(() {
managerId = ref.read(managerIdProvider);
_loadInvoicePolicies();
});
}
@override
void dispose() {
_searchStaffController.dispose();
super.dispose();
}
Future<void> _loadInvoicePolicies() async {
final invoiceId = widget.viewItem['id'];
if (invoiceId == null) {
setState(() {
filteredPolicies = [];
masterPolicies = [];
});
return;
}
setState(() => isLoading = true);
try {
final jsondata = {
'invoice_id': invoiceId,
'manager_id': managerId,
};
final response = await apiService.getViewInvoiceList(jsondata);
if (response['status'] == 'success') {
final payload = response['data'];
List<Map<String, dynamic>> items = [];
Map<String, dynamic>? invoiceMap;
if (payload is Map) {
final m = Map<String, dynamic>.from(payload);
invoiceMap = m['invoice'] is Map
? Map<String, dynamic>.from(m['invoice'] as Map)
: null;
final rawItems = m['items'];
if (rawItems is List) {
items = rawItems
.map((e) => Map<String, dynamic>.from(e as Map))
.toList();
}
} else if (payload is List) {
items = List<Map<String, dynamic>>.from(payload);
}
num commissionSum = 0;
for (final row in items) {
commissionSum +=
num.tryParse(row['commission_amount']?.toString() ?? '0') ?? 0;
}
final totPolicy = (invoiceMap?['total_policies'] ?? items.length)
.toString();
final totalCommiss =
invoiceMap?['total_commission'] ?? commissionSum;
setState(() {
totalPolicies = totPolicy;
totalCommission = totalCommiss;
masterPolicies = items;
filteredPolicies = List<Map<String, dynamic>>.from(items);
_currentPage = 1;
});
} else {
setState(() {
masterPolicies = [];
filteredPolicies = [];
});
}
} catch (_) {
setState(() {
masterPolicies = [];
filteredPolicies = [];
});
} finally {
if (mounted) setState(() => isLoading = false);
}
}
void filterPolicyData(String query) {
final q = query.trim();
setState(() {
_currentPage = 1;
if (q.isEmpty) {
filteredPolicies = List.from(masterPolicies);
return;
}
filteredPolicies = masterPolicies.where((item) {
return (item['policy_no'] ?? '').toString().contains(q) ||
(item['customer_name'] ?? '').toString().contains(q) ||
(item['agent_name'] ?? '').toString().contains(q) ||
(item['insurer_name'] ?? '').toString().contains(q) ||
(item['premium_amount'] ?? '').toString().contains(q) ||
(item['commission_amount'] ?? '').toString().contains(q) ||
_formatDate(item['issued_date']?.toString() ?? '').contains(q);
}).toList();
});
}
String _displayText(dynamic value) {
if (value == null) return '-';
final text = value.toString().trim();
if (text.isEmpty || text.toLowerCase() == 'null') return '-';
return text;
}
String _invoiceDateLabel() {
final ui = widget.viewItem['invoice_date_ui_format'];
if (ui != null && ui.toString().trim().isNotEmpty) {
return ui.toString();
}
final raw = widget.viewItem['invoice_date'];
if (raw == null) return '-';
try {
return DateFormat('dd-MM-yyyy').format(DateTime.parse(raw.toString()));
} catch (_) {
return raw.toString();
}
}
String _formatDate(String? rawDate) {
if (rawDate == null || rawDate.isEmpty) return '-';
try {
return DateFormat('dd-MM-yyyy').format(DateTime.parse(rawDate));
} catch (_) {
return rawDate;
}
}
String _amountText(dynamic value) {
final text = _displayText(value);
if (text == '-') return '-';
return text.replaceAll('', '').trim();
}
Widget _headerText(
String text,
int flex, {
TextAlign align = TextAlign.left,
}) {
return Expanded(
flex: flex,
child: Text(
text,
textAlign: align,
style: GoogleFonts.poppins(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.blueGrey,
letterSpacing: 0.5,
),
),
);
}
Widget _cell(
String? value,
int flex, {
TextAlign align = TextAlign.left,
Color? color,
FontWeight? fontWeight,
}) {
return Expanded(
flex: flex,
child: Text(
value ?? '-',
textAlign: align,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 11,
color: color,
fontWeight: fontWeight,
),
),
);
}
Widget _policyTableHeader() {
return Container(
height: 30,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
color: Color(0xFFf9fafb),
border: Border(bottom: BorderSide(color: Color(0xFFf9fafb))),
),
child: Row(
children: [
_headerText('S.NO', 5, align: TextAlign.center),
_headerText('POLICY NO', 14),
_headerText('POLICY DATE', 10, align: TextAlign.center),
_headerText('PARTNER', 20),
_headerText('CUSTOMER', 16),
_headerText('PREMIUM (₹)', 11, align: TextAlign.right),
_headerText('PAYOUT (₹)', 11, align: TextAlign.right),
],
),
);
}
Widget _policyRow(Map<String, dynamic> p, int index) {
return Container(
height: 38,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: Color(0xFFf9fafb))),
),
child: Row(
children: [
_cell('${index + 1}', 5, align: TextAlign.center),
_cell(p['policy_no']?.toString(), 14),
_cell(_formatDate(p['issued_date']?.toString()), 10, align: TextAlign.center),
_cell(
(p['agent_code'] != null && p['agent_name'] != null)
? '${p['agent_code']} - ${p['agent_name']}'
: (p['agent_name'] ?? '-').toString(),
20,
),
_cell(p['customer_name']?.toString(), 16),
_cell(
_amountText(p['premium_amount']),
11,
align: TextAlign.right,
),
_cell(
_amountText(p['commission_amount']),
11,
align: TextAlign.right,
color: const Color(0xFF009B77),
fontWeight: FontWeight.w600,
),
],
),
);
}
int get _totalPages {
if (filteredPolicies.isEmpty) return 1;
return (filteredPolicies.length / _rowsPerPage).ceil();
}
List<Map<String, dynamic>> get _paginatedPolicies {
if (filteredPolicies.isEmpty) return const <Map<String, dynamic>>[];
final start = (_currentPage - 1) * _rowsPerPage;
final end = (start + _rowsPerPage).clamp(0, filteredPolicies.length);
return filteredPolicies.sublist(start, end);
}
void _goToPage(int page) {
if (page < 1 || page > _totalPages) return;
setState(() => _currentPage = page);
}
List<Map<String, dynamic>> get _exportRows {
return filteredPolicies.map((p) {
return {
'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(),
'customer': p['customer_name'],
'premium': _amountText(p['premium_amount']),
'payout': _amountText(p['commission_amount']),
};
}).toList();
}
@override
Widget build(BuildContext context) {
final statusRaw = widget.viewItem['payout_status'];
final statusInt = int.tryParse(statusRaw?.toString() ?? '0') ?? 0;
final isPending = statusInt == 1;
final invoiceNo = _displayText(widget.viewItem['invoice_no']);
return MainLayout(
title: 'Pay Out',
body: isLoading
? Container(
color: Colors.white,
height: MediaQuery.of(context).size.height * 0.7,
child: const Center(child: CircularProgressIndicator()),
)
: Container(
color: Colors.white,
padding: const EdgeInsets.all(8),
// margin: EdgeInsets.symmetric(
// horizontal: MediaQuery.of(context).size.width * 0.1,
// ),
child: Column(
children: [
SizedBox(
height: 40,
width: MediaQuery.of(context).size.width,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Tooltip(
message: 'Back',
child: IconButton(
icon: const Icon(
Icons.arrow_left_sharp,
size: 25,
color: Color(0xFF425B5B),
),
onPressed: () => context.go(AppRoutes.payoutList),
splashRadius: 18,
hoverColor: Colors.black12,
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(),
),
),
const SizedBox(width: 5),
Text(
'View Payout Details',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const Spacer(),
Text(
invoiceNo,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w500,
color: const Color(0xFF374151),
),
),
const SizedBox(width: 10),
Text(
'(${_invoiceDateLabel()})',
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w300,
color: const Color(0xFF6B7280),
),
),
const SizedBox(width: 10),
Text(
'Status: ${isPending ? 'Pending' : 'Completed'}',
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w300,
color: const Color(0xFF6B7280),
),
),
const Spacer(),
ThemedSearchField(
hintText: 'Search',
onChanged: filterPolicyData,
controller: _searchStaffController,
backgroundColor: const Color(0xFFFFFFFF),
txtHeight: 30,
txtwidth: ResponsiveLayout.isMobile(context)
? MediaQuery.of(context).size.width * 0.7
: MediaQuery.of(context).size.width * 0.15,
),
const SizedBox(width: 10),
ExportBtn(
sheetName: 'Payout Details',
fileName:
'payout_details_${DateFormat('yyyyMMdd_HHmmss').format(DateTime.now())}',
data: _exportRows,
displayHeaders: const [
'S.NO',
'POLICY NO',
'POLICY DATE',
'PARTNER',
'CUSTOMER',
'PREMIUM (₹)',
'PAYOUT (₹)',
],
keys: const [
'sno',
'policy_no',
'policy_date',
'partner',
'customer',
'premium',
'payout',
],
),
],
),
),
Expanded(
child: Card(
color: Colors.white,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
child: ScrollConfiguration(
behavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.trackpad,
},
),
child: LayoutBuilder(
builder: (context, constraints) {
final rows = _paginatedPolicies;
final startIdx = (filteredPolicies.isEmpty)
? 0
: ((_currentPage - 1) * _rowsPerPage);
return Column(
children: [
_policyTableHeader(),
Expanded(
child: filteredPolicies.isEmpty
? const Center(
child: Text(
'No policies in this payout',
style: TextStyle(
fontSize: 12,
color: Colors.blueGrey,
),
),
)
: ListView.builder(
itemCount: rows.length,
itemBuilder: (context, index) {
return _policyRow(
rows[index],
startIdx + index,
);
},
),
),
],
);
},
),
),
),
),
Container(
padding: const EdgeInsets.all(8),
color: const Color(0xFFf9fafb),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'$totalPolicies',
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.blueGrey,
),
),
const SizedBox(width: 4),
Text(
'Policies',
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.blueGrey,
),
),
const SizedBox(width: 4),
Text(
'|',
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.blueGrey,
),
),
const SizedBox(width: 4),
Text(
'Total Payout Amount',
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.blueGrey,
),
),
const SizedBox(width: 4),
Text(
'$totalCommission',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.teal,
),
),
],
),
),
if (filteredPolicies.isNotEmpty)
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 8, 2),
child: Align(
alignment: Alignment.centerRight,
child: PaginationControls(
currentPage: _currentPage,
itemsPerPage: _rowsPerPage,
totalItems: filteredPolicies.length,
onPageChanged: (page) => _goToPage(page),
onItemsPerPageChanged: (items) {
setState(() {
_rowsPerPage = items;
_currentPage = 1;
});
},
),
),
),
],
),
),
);
}
}