merge
This commit is contained in:
commit
725945d35b
@ -36,6 +36,7 @@ import '../../presentation/screens/home/home_screen.dart';
|
||||
import '../../presentation/screens/login/login_screen.dart';
|
||||
import '../../presentation/screens/payout/payout_screen.dart';
|
||||
import '../../presentation/screens/payout/payout_details.dart';
|
||||
import '../../presentation/screens/payout/payout_details_view.dart';
|
||||
import '../../presentation/screens/payout/payout_list.dart';
|
||||
import '../../presentation/screens/payout/payout_upload.dart';
|
||||
import '../../presentation/screens/payout/payout_report.dart';
|
||||
@ -99,9 +100,18 @@ final GoRouter appRouter = GoRouter(
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.payoutDetails,
|
||||
builder: (context, state) => const PayOutDetails(),
|
||||
),
|
||||
GoRoute(
|
||||
path: AppRoutes.payoutDetailsView,
|
||||
builder: (context, state) {
|
||||
final editItem = state.extra as Map<String, dynamic>?;
|
||||
return PayOutDetails(editItem: editItem);
|
||||
final extra = state.extra;
|
||||
if (extra is Map) {
|
||||
return PayOutDetailsView(
|
||||
viewItem: Map<String, dynamic>.from(extra),
|
||||
);
|
||||
}
|
||||
return PayOutDetailsView(viewItem: <String, dynamic>{});
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
|
||||
@ -52,8 +52,8 @@ class AppRoutes {
|
||||
static const String vehicleTypeLst = '/vehicleTypeLst';
|
||||
static const String payoutList = '/payoutList';
|
||||
static const String payoutDetails = '/payoutDetails';
|
||||
static const String payoutDetailsView = '/payoutDetailsView';
|
||||
static const String payoutUpload = '/payoutUpload';
|
||||
/// Accounts + agent: commission payout report (list API TBD).
|
||||
static const String payoutReport = '/payoutReport';
|
||||
static const String payoutGrid = '/payoutGrid';
|
||||
static const String gridList = '/gridList';
|
||||
|
||||
@ -2061,8 +2061,7 @@ class ApiService {
|
||||
// --------------------------------- PayOut Module----------------------------------------------
|
||||
Future<Map<String, dynamic>> getCommissionRateList(data) async {
|
||||
print("getCommissionRateList------- $data}");
|
||||
final url = Uri.parse('${Env.apiUrl}/invoice/commission-rate-list');
|
||||
// final url = Uri.parse('http://localhost/nhance_partner_be/invoice/commission-rate-list');
|
||||
final url = Uri.parse('${Env.apiUrl}invoice/commission-rate-list');
|
||||
print("getCommissionRateList 1");
|
||||
// final token = await getToken(); // Fetch token
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@ -13,15 +12,10 @@ import '../../layouts/main_layout.dart';
|
||||
import '../../layouts/responsive_layout.dart';
|
||||
import '../../providers/manager_provider.dart';
|
||||
import '../../providers/userRoleProvider.dart';
|
||||
import '../../themes/indicators/input_field_decoration.dart';
|
||||
import '../../themes/indicators/search_field_theme.dart';
|
||||
import 'FormFieldBox.dart';
|
||||
import 'custom_dateRange.dart';
|
||||
|
||||
class PayOutDetails extends ConsumerStatefulWidget {
|
||||
final Map<String, dynamic>? editItem;
|
||||
|
||||
const PayOutDetails({super.key, this.editItem});
|
||||
const PayOutDetails({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<PayOutDetails> createState() => _PayOutDetailsState();
|
||||
@ -47,12 +41,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
|
||||
dynamic totalPolicies = '0';
|
||||
dynamic totalCommission = '0';
|
||||
bool isEdit = false;
|
||||
|
||||
List<Map<String, dynamic>> masterPolicies = [];
|
||||
|
||||
final TextEditingController _searchStaffController = TextEditingController();
|
||||
|
||||
// --------------------------
|
||||
// Filters
|
||||
// --------------------------
|
||||
@ -90,7 +81,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
List<Map<String, dynamic>> filteredAgentData = [];
|
||||
|
||||
bool isLoading = false;
|
||||
bool isLoadingEditData = false;
|
||||
bool hasFetchedTableData = false;
|
||||
final Set<int> _inlineSavingIds = <int>{};
|
||||
late ApiService apiService;
|
||||
@ -121,24 +111,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
}
|
||||
|
||||
await getBroker();
|
||||
|
||||
// ✅ EDIT MODE (MUST be here)
|
||||
if (widget.editItem != null) {
|
||||
print('PD => editItemID ${widget.editItem}');
|
||||
|
||||
final invoiceID = widget.editItem!['id'];
|
||||
|
||||
controllers['invoiceNo']?.text =
|
||||
widget.editItem!['invoice_no'];
|
||||
|
||||
controllers['invoiceDate']?.text =
|
||||
widget.editItem!['invoice_date_ui_format'];
|
||||
|
||||
controllers['invoiceStatus']?.text =
|
||||
widget.editItem!['payout_status'];
|
||||
|
||||
_loadEditData(invoiceID, managerId); // ✅ managerId ready
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -175,9 +147,9 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
(item['commission_amount'] ?? '').toString().toLowerCase().contains(
|
||||
lowerQuery,
|
||||
) ||
|
||||
(_formatDate(item['issued_date']) ?? '').toLowerCase().contains(
|
||||
lowerQuery,
|
||||
);
|
||||
_formatDate(item['issued_date']?.toString() ?? '')
|
||||
.toLowerCase()
|
||||
.contains(lowerQuery);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
@ -200,138 +172,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
});
|
||||
}
|
||||
|
||||
void _loadEditData(String invoiceID, managerID) async {
|
||||
setState(() => isLoadingEditData = true);
|
||||
print("H010 => r : $roleId | mId: $managerID | ParmMID: $managerID | uId: $userId ");
|
||||
print('PD => _loadEditData');
|
||||
|
||||
final jsondata = {
|
||||
// "id": invoiceID,
|
||||
"invoice_id": invoiceID,
|
||||
"manager_id": managerId
|
||||
|
||||
// "issued_date": formattedTillDate,
|
||||
};
|
||||
print('PD => _loadEditData - $jsondata');
|
||||
final response = await apiService.getCommissionRateList(jsondata);
|
||||
// final response = await apiService.getInvoiceDetails(invoiceID);
|
||||
|
||||
if (response['status'] == 'success') {
|
||||
print('PD => _loadEditData success');
|
||||
print('PD => _loadEditData success - ${response['data']}');
|
||||
final invoice = response['data'];
|
||||
print("Invoice → $invoice");
|
||||
// final items = List<Map<String, dynamic>>.from(response['data']['items']);
|
||||
final items = response['data'];
|
||||
final totPolicy = response['total_policies'].toString() ?? "0";
|
||||
final totalCommiss = response['total_commission'];
|
||||
|
||||
setState(() {
|
||||
// selectedPolicies = response['total_policies'];
|
||||
isEdit = true;
|
||||
totalPolicies = totPolicy;
|
||||
totalCommission = totalCommiss;
|
||||
|
||||
// filteredPolicies = items.map((p) {
|
||||
// final policyId = int.parse(p['policy_id']);
|
||||
// return {...p, 'is_selected': selectedPolicies.contains(policyId)};
|
||||
// }).toList();
|
||||
|
||||
final List<Map<String, dynamic>> items = (response['data'] as List)
|
||||
.map((e) => Map<String, dynamic>.from(e))
|
||||
.toList();
|
||||
|
||||
masterPolicies = List<Map<String, dynamic>>.from(response['data']);
|
||||
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
|
||||
|
||||
print("selectedPolicies → $filteredPolicies ");
|
||||
});
|
||||
print(
|
||||
"selectedPolicies → $selectedPolicies ->${response['total_policies']}",
|
||||
);
|
||||
print("Items → $items");
|
||||
|
||||
// -----------------------------
|
||||
// 1. SET BASIC FIELDS
|
||||
// -----------------------------
|
||||
// setState(() {
|
||||
// // selectedAgentId = int.tryParse(invoice['agent_id'].toString());
|
||||
//
|
||||
// // invoiceNoController.text = invoice['invoice_no'];
|
||||
// // invoiceDate = DateTime.parse(invoice['invoice_date']);
|
||||
//
|
||||
// controllers['startDate']?.text = invoice['from_date'];
|
||||
// controllers['endDate']?.text = invoice['to_date'];
|
||||
// selectedBrokerID = int.tryParse(invoice['broker_id'].toString());
|
||||
// if (invoice['agent_id'] is List) {
|
||||
// selectedAgentId = (invoice['agent_id'] as List)
|
||||
// .map((e) => e.toString())
|
||||
// .toList();
|
||||
// } else if (invoice['agent_id'] is String) {
|
||||
// final raw = invoice['agent_id'].toString();
|
||||
//
|
||||
// if (raw.startsWith('[')) {
|
||||
// // JSON string: "[1,2]"
|
||||
// selectedAgentId = List<String>.from(
|
||||
// jsonDecode(raw).map((e) => e.toString()),
|
||||
// );
|
||||
// } else if (raw.contains(',')) {
|
||||
// // Comma separated: "1,2"
|
||||
// selectedAgentId = raw.split(',').map((e) => e.trim()).toList();
|
||||
// } else if (raw.isNotEmpty) {
|
||||
// // Single value: "1"
|
||||
// selectedAgentId = [raw];
|
||||
// } else {
|
||||
// selectedAgentId = [];
|
||||
// }
|
||||
// } else {
|
||||
// selectedAgentId = [];
|
||||
// }
|
||||
//
|
||||
// // if (invoice['till_date'] != "0000-00-00") {
|
||||
// // policyTillDate = DateTime.parse(invoice['till_date']);
|
||||
// // } else {
|
||||
// // policyTillDate = DateTime.now();
|
||||
// // }
|
||||
// });
|
||||
// print('PD => selectedBrokerID $selectedBrokerID');
|
||||
|
||||
// -----------------------------
|
||||
// 3. LOAD ALL POLICIES FOR THIS BROKER + AGENT
|
||||
// -----------------------------
|
||||
// await loadPolicies();
|
||||
|
||||
// -----------------------------
|
||||
// 2. SET SELECTED POLICIES
|
||||
// -----------------------------
|
||||
// selectedPolicies = items
|
||||
// .map<int>((p) => int.parse(p['policy_id'].toString()))
|
||||
// .toSet();
|
||||
|
||||
// -----------------------------
|
||||
// 3. LOAD ALL POLICIES FOR THIS BROKER + AGENT
|
||||
// -----------------------------
|
||||
// await loadPolicies();
|
||||
|
||||
// -----------------------------
|
||||
// 4. KEEP ONLY POLICIES THAT MATCH API DATA
|
||||
// -----------------------------
|
||||
// setState(() {
|
||||
// filteredPolicies = filteredPolicies.map((p) {
|
||||
// final policyId = int.parse(p['policy_id']);
|
||||
// return {...p, 'is_selected': selectedPolicies.contains(policyId)};
|
||||
// }).toList();
|
||||
// });
|
||||
}
|
||||
|
||||
setState(() => isLoadingEditData = false);
|
||||
}
|
||||
|
||||
String formatDate(String date) {
|
||||
final d = DateTime.parse(date);
|
||||
return DateFormat('dd-MM-yyyy').format(d);
|
||||
}
|
||||
|
||||
// Get Broker List
|
||||
Future<void> getBroker() async {
|
||||
print('PD =>getBroker called');
|
||||
@ -452,7 +292,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
"from_date": controllers['startDate']?.text,
|
||||
"to_date": controllers['endDate']?.text,
|
||||
"manager_id": managerId,
|
||||
if (agentIds != null && agentIds.isNotEmpty) "agent_id": agentIds,
|
||||
if (agentIds.isNotEmpty) "agent_id": agentIds,
|
||||
// "agent_id": agentIds,
|
||||
// "issued_date": formattedTillDate,
|
||||
};
|
||||
@ -672,13 +512,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
}
|
||||
}
|
||||
|
||||
String _displayText(dynamic value) {
|
||||
if (value == null) return '-';
|
||||
final text = value.toString().trim();
|
||||
if (text.isEmpty || text.toLowerCase() == 'null') return '-';
|
||||
return text;
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> _buildUtrPayloadForSelectedPolicies({
|
||||
required String defaultUtrDate,
|
||||
}) {
|
||||
@ -770,23 +603,11 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final df = DateFormat("dd-MM-yyyy");
|
||||
final int status =
|
||||
int.tryParse(controllers['invoiceStatus']?.text ?? '0') ?? 0;
|
||||
|
||||
final bool isPending = status == 1;
|
||||
|
||||
return MainLayout(
|
||||
title: "Pay Out",
|
||||
body: !isLoadingEditData
|
||||
? Container(
|
||||
body: Container(
|
||||
color: Colors.white,
|
||||
padding: const EdgeInsets.all(8),
|
||||
margin: isEdit
|
||||
? EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width * 0.1,
|
||||
)
|
||||
: null,
|
||||
child: Column(
|
||||
children: [
|
||||
// const SizedBox(height: 20),
|
||||
@ -828,60 +649,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
),
|
||||
),
|
||||
|
||||
// 🟢 Status Chip
|
||||
if (isEdit) ...[
|
||||
Spacer(),
|
||||
Text(
|
||||
controllers['invoiceNo']?.text ?? '',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF374151),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
// controllers['invoiceDate']?.text ?? '',
|
||||
'(${controllers['invoiceDate']?.text ?? ''})',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: const Color(0xFF6B7280),
|
||||
),
|
||||
),
|
||||
// SizedBox(width: 10),
|
||||
// Spacer(),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'Status: ${isPending ? 'Pending' : 'Completed'}',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: const Color(0xFF6B7280),
|
||||
// color: isPending
|
||||
// ? Colors.blueAccent
|
||||
// : const Color(0xFF047857),
|
||||
),
|
||||
),
|
||||
|
||||
Spacer(),
|
||||
|
||||
ThemedSearchField(
|
||||
hintText: 'Search',
|
||||
|
||||
// backgroundColor: Color(0xFFF6F8F8),
|
||||
onChanged: filterPolicyData,
|
||||
|
||||
controller: _searchStaffController,
|
||||
backgroundColor: Color(0xFFFFFFFF),
|
||||
txtHeight: 30,
|
||||
txtwidth: ResponsiveLayout.isMobile(context)
|
||||
? MediaQuery.of(context).size.width * 0.7
|
||||
: MediaQuery.of(context).size.width * 0.15,
|
||||
),
|
||||
],
|
||||
|
||||
if (!isEdit) ...[
|
||||
const Spacer(),
|
||||
if (hasFetchedTableData) ...[
|
||||
const SizedBox(width: 10),
|
||||
@ -971,13 +738,11 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// SizedBox(height: 5),
|
||||
if (!isEdit) ...[
|
||||
Container(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: DateFilterRowPayout(
|
||||
@ -1017,7 +782,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
),
|
||||
|
||||
SizedBox(height: 10),
|
||||
],
|
||||
// Policy Selection
|
||||
Card(
|
||||
// color: Colors.amber,
|
||||
@ -1157,80 +921,10 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
if (isEdit) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
// color: Colors.amber,
|
||||
color: Color(0xFFf9fafb),
|
||||
// color: Colors.white,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
// selectedPolicies.length.toString(),
|
||||
totalPolicies,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
"policies selected",
|
||||
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),
|
||||
|
||||
/// NULL-SAFE TOTAL COMMISSION CALCULATION
|
||||
Text(
|
||||
// "₹ ${_calculateTotalCommission()}",
|
||||
"₹ $totalCommission",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.teal,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
// ----------------------
|
||||
// const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Container(
|
||||
color: Colors.white,
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -1265,7 +959,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (!isEdit) ...[
|
||||
SizedBox(
|
||||
width: colCheck,
|
||||
child: Checkbox(
|
||||
@ -1284,11 +977,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
)
|
||||
.where((id) => id != 0) // Filter out invalid IDs
|
||||
.toSet();
|
||||
// selectedPolicies = filteredPolicies
|
||||
// .map(
|
||||
// (p) => int.tryParse(p["policy_id"].toString()) ?? 0,
|
||||
// )
|
||||
// .toSet();
|
||||
} else {
|
||||
selectedPolicies.clear();
|
||||
}
|
||||
@ -1298,7 +986,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
_headerText("POLICY NO", colPolicy),
|
||||
_headerText("POLICY DATE", colDate),
|
||||
_headerText("PARTNER", colAgent),
|
||||
@ -1327,10 +1014,7 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
|
||||
Widget _policyTableBody() {
|
||||
return SizedBox(
|
||||
// height: isEdit ? 400 : 340, // avoids overflow
|
||||
height: isEdit
|
||||
? MediaQuery.of(context).size.height * 0.65
|
||||
: MediaQuery.of(context).size.height * 0.48, // avoids overflow
|
||||
height: MediaQuery.of(context).size.height * 0.48,
|
||||
child: ListView.builder(
|
||||
itemCount: filteredPolicies.length,
|
||||
itemBuilder: (context, index) {
|
||||
@ -1362,7 +1046,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (!isEdit) ...[
|
||||
SizedBox(
|
||||
width: colCheck,
|
||||
child: Checkbox(
|
||||
@ -1378,7 +1061,6 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
_cell(p["policy_no"], colPolicy),
|
||||
// _cell(p["issued_date"] ?? "-", colDate),
|
||||
_cell(_formatDate(p['issued_date']), colDate),
|
||||
@ -1612,16 +1294,15 @@ class _PayOutDetailsState extends ConsumerState<PayOutDetails> {
|
||||
}
|
||||
|
||||
static final double colCheck = 44;
|
||||
double get colPolicy => isEdit ? 150 : 200;
|
||||
double get colDate => isEdit ? 120 : 200;
|
||||
static final double colPolicy = 200;
|
||||
static final double colDate = 200;
|
||||
static final double colAgent = 200;
|
||||
static final double colCustomer = 200;
|
||||
static final double colPremium = 200;
|
||||
// static final double colCommission = 200;
|
||||
static final double colCommission = 200;
|
||||
|
||||
double get colCommission => isEdit ? 120 : 200;
|
||||
double get _tableMinWidth =>
|
||||
(isEdit ? 0 : colCheck) +
|
||||
colCheck +
|
||||
colPolicy +
|
||||
colDate +
|
||||
colAgent +
|
||||
|
||||
419
lib/presentation/screens/payout/payout_details_view.dart
Normal file
419
lib/presentation/screens/payout/payout_details_view.dart
Normal file
@ -0,0 +1,419 @@
|
||||
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 '../../../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/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';
|
||||
|
||||
static const double colPolicy = 150;
|
||||
static const double colDate = 120;
|
||||
static const double colAgent = 200;
|
||||
static const double colCustomer = 200;
|
||||
static const double colPremium = 200;
|
||||
static const double colCommission = 120;
|
||||
|
||||
double get _tableMinWidth =>
|
||||
colPolicy + colDate + colAgent + colCustomer + colPremium + colCommission + 32;
|
||||
|
||||
@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.getCommissionRateList(jsondata);
|
||||
if (response['status'] == 'success') {
|
||||
final totPolicy = (response['total_policies'] ?? 0).toString();
|
||||
final totalCommiss = response['total_commission'];
|
||||
setState(() {
|
||||
totalPolicies = totPolicy;
|
||||
totalCommission = totalCommiss;
|
||||
masterPolicies = List<Map<String, dynamic>>.from(response['data'] ?? []);
|
||||
filteredPolicies = List<Map<String, dynamic>>.from(response['data'] ?? []);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
masterPolicies = [];
|
||||
filteredPolicies = [];
|
||||
});
|
||||
}
|
||||
} catch (_) {
|
||||
setState(() {
|
||||
masterPolicies = [];
|
||||
filteredPolicies = [];
|
||||
});
|
||||
} finally {
|
||||
if (mounted) setState(() => isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
void filterPolicyData(String query) {
|
||||
final lowerQuery = query.toLowerCase();
|
||||
setState(() {
|
||||
if (query.trim().isEmpty) {
|
||||
filteredPolicies = List.from(masterPolicies);
|
||||
return;
|
||||
}
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _headerText(String text, double width) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
child: Text(
|
||||
text,
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.blueGrey,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _cell(String? value, double width) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
child: Text(
|
||||
value ?? '-',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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('POLICY NO', colPolicy),
|
||||
_headerText('POLICY DATE', colDate),
|
||||
_headerText('PARTNER', colAgent),
|
||||
_headerText('CUSTOMER', colCustomer),
|
||||
_headerText('PREMIUM', colPremium),
|
||||
_headerText('PAYOUT', colCommission),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _policyTableBody() {
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height * 0.65,
|
||||
child: ListView.builder(
|
||||
itemCount: filteredPolicies.length,
|
||||
itemBuilder: (context, index) {
|
||||
final p = filteredPolicies[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(p['policy_no']?.toString(), colPolicy),
|
||||
_cell(_formatDate(p['issued_date']?.toString()), colDate),
|
||||
_cell(
|
||||
(p['agent_code'] != null && p['agent_name'] != null)
|
||||
? '${p['agent_code']} - ${p['agent_name']}'
|
||||
: (p['agent_name'] ?? '-').toString(),
|
||||
colAgent,
|
||||
),
|
||||
_cell(p['customer_name']?.toString(), colCustomer),
|
||||
_cell('₹${p['premium_amount']}', colPremium),
|
||||
SizedBox(
|
||||
width: colCommission,
|
||||
child: Text(
|
||||
'₹${_displayText(p['commission_amount'])}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF009B77),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Card(
|
||||
color: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
elevation: 0,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: SizedBox(
|
||||
width: _tableMinWidth,
|
||||
child: Column(
|
||||
children: [
|
||||
_policyTableHeader(),
|
||||
_policyTableBody(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1015,9 +1015,8 @@ class _PayoutListState extends ConsumerState<PayoutList> {
|
||||
),
|
||||
tooltip: 'View Payout',
|
||||
onPressed: () {
|
||||
// TODO: handle edit
|
||||
context.go(AppRoutes.payoutDetails, extra: item);
|
||||
print("PL 9 => Edit clicked for ${item['invoice_no']}");
|
||||
context.go(AppRoutes.payoutDetailsView, extra: item);
|
||||
print("PL 9 => View clicked for ${item['invoice_no']}");
|
||||
},
|
||||
splashRadius: 16,
|
||||
hoverColor: Colors.black12,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user