nhance_partner/lib/presentation/screens/payout/payout_old.dart
2026-01-08 16:01:06 +05:30

1072 lines
43 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';
import 'package:nhance_partner/data/utils/toastNotification.dart';
import '../../../core/routing/routes.dart';
import '../../../core/services/api_service.dart';
import '../../layouts/main_layout.dart';
import '../../providers/manager_provider.dart';
import 'FormFieldBox.dart';
class PayOutScreen extends ConsumerStatefulWidget {
final Map<String, dynamic>? editItem;
const PayOutScreen({super.key, this.editItem});
@override
ConsumerState<PayOutScreen> createState() => _PayOutScreenState();
}
class _PayOutScreenState extends ConsumerState<PayOutScreen> {
// --------------------------
// Invoice Fields
// --------------------------
late TextEditingController invoiceNoController;
DateTime invoiceDate = DateTime.now();
int? selectedAgentId;
int? selectedBrokerID;
DateTime? policyTillDate = DateTime.now();
// --------------------------
// Filters
// --------------------------
DateTime? fromDate;
DateTime? toDate;
// --------------------------
// Collapse States
// --------------------------
bool invoiceCollapsed = false;
bool policyCollapsed = false;
// --------------------------
// Sample Data (replace with API later)
// --------------------------
final List<Map<String, dynamic>> allPolicies = [];
List<Map<String, dynamic>> filteredPolicies = [];
Set<int> selectedPolicies = {};
List<Map<String, dynamic>> getBrokerData = [];
List<Map<String, dynamic>> filteredBrokerData = [];
List<Map<String, dynamic>> getAgentData = [];
List<Map<String, dynamic>> filteredAgentData = [];
bool isLoading = false;
late ApiService apiService;
dynamic managerId;
dynamic userId;
final DateFormat df = DateFormat('dd-MM-yyyy');
@override
void initState() {
super.initState();
apiService = ApiService();
invoiceNoController = TextEditingController(text: generateInvoiceNumber());
Future.microtask(() async {
managerId = ref.watch(managerIdProvider);
userId = ref.watch(userIdProvider);
if (managerId != null) {
await getAgentList(managerId);
}
await getBroker();
});
// 👉 CHECK EDIT MODE
if (widget.editItem != null) {
print('editItemID ${widget.editItem}');
final invoiceID = widget.editItem!['id'];
_loadEditData(invoiceID);
}
}
void _loadEditData(String invoiceID) async {
setState(() => isLoading = true);
final response = await apiService.getInvoiceDetails(invoiceID);
if (response['status'] == 'success') {
final invoice = response['data']['invoice'];
final items = List<Map<String, dynamic>>.from(response['data']['items']);
print("Invoice → $invoice");
print("Items → $items");
// -----------------------------
// 1. SET BASIC FIELDS
// -----------------------------
setState(() {
selectedBrokerID = int.tryParse(invoice['broker_id'].toString());
selectedAgentId = int.tryParse(invoice['agent_id'].toString());
invoiceNoController.text = invoice['invoice_no'];
invoiceDate = DateTime.parse(invoice['invoice_date']);
if (invoice['till_date'] != "0000-00-00") {
policyTillDate = DateTime.parse(invoice['till_date']);
} else {
policyTillDate = DateTime.now();
}
});
print('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(() => isLoading = 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('getBroker called');
setState(() {
isLoading = 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(() {
isLoading = false;
});
}
}
//Get Agent List
Future<void> getAgentList(int id) async {
print('getClaimList called');
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchAgentUserList(managerId);
if (response['status'] == 'success') {
print('getAgentListData - ${response['data']}');
setState(() {
getAgentData = List<Map<String, dynamic>>.from(response['data']);
print('API Data - $getAgentData');
filteredAgentData = List.from(getAgentData);
print('originalData - $filteredAgentData');
});
} else {
getAgentData = [];
filteredAgentData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
// Generate invoice number
String generateInvoiceNumber() {
final now = DateTime.now();
final random = now.millisecondsSinceEpoch % 1000;
return "INV${now.year}${now.month.toString().padLeft(2, '0')}${random.toString().padLeft(3, '0')}";
}
// Pick date helper
Future<DateTime?> pickDate(DateTime initial) async {
return await showDatePicker(
context: context,
initialDate: initial,
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
}
// Load policies (simulate API)
Future<void> loadPolicies() async {
try {
String formattedTillDate = DateFormat(
'yyyy-MM-dd',
).format(policyTillDate!);
final jsondata = {
"broker_id": selectedBrokerID,
"agent_id": selectedAgentId,
"issued_date": formattedTillDate,
};
final response = await apiService.getCommissionRateList(jsondata);
if (response['status'] == 'success') {
print('API Data response - $response');
setState(() {
filteredPolicies = List<Map<String, dynamic>>.from(response['data']);
print('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();
});
} else {
filteredPolicies = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
// Apply date filters
void filterPolicies() {
if (selectedAgentId == null) return;
setState(() {
filteredPolicies = allPolicies.where((p) {
final date = DateTime.parse(p["date"]);
// if (p["agentId"] != selectedAgentId) return false;
// if (policyTillDate != null && date.isAfter(policyTillDate!))
// return false;
if (fromDate != null && date.isBefore(fromDate!)) return false;
if (toDate != null && date.isAfter(toDate!)) return false;
return true;
}).toList();
});
}
// Save invoice (simulation)
Future<void> saveInvoice() async {
try {
if (selectedAgentId == null) {
ToastHelper.showWarningToast(context, "Please select an agent");
return;
}
if (selectedPolicies.isEmpty) {
ToastHelper.showWarningToast(
context,
"Please select at least 1 policy",
);
return;
}
// Format dates for API
final String formattedInvoiceDate = DateFormat(
'yyyy-MM-dd',
).format(invoiceDate);
final String formattedTillDate = DateFormat(
'yyyy-MM-dd',
).format(policyTillDate!);
// Total Commission
final double totalCommission = filteredPolicies
.where((p) => selectedPolicies.contains(int.parse(p["policy_id"])))
.fold(0.0, (sum, p) => sum + double.parse(p["commission_amount"]));
// Build items array
final List<Map<String, dynamic>> items = filteredPolicies
.where((p) => selectedPolicies.contains(int.parse(p["policy_id"])))
.map(
(p) => {
"policy_id": int.parse(p["policy_id"]),
"policy_no": p["policy_no"],
"commission_amount": double.parse(p["commission_amount"]),
},
)
.toList();
final jsondata = {
"invoice_no": invoiceNoController.text,
"invoice_amount": totalCommission,
"agent_id": selectedAgentId,
"broker_id": selectedBrokerID,
"invoice_date": formattedInvoiceDate,
"till_date": formattedTillDate,
"payout_status": 0,
"created_by": userId,
"updated_by": userId,
"items": items,
};
print('Commissions $jsondata');
final response = await apiService.getCreateOrUpdate(jsondata);
if (response['status'] == 'success') {
setState(() {
print('API Data response - $response');
selectedPolicies.clear();
invoiceNoController.clear();
invoiceDate = DateTime.now();
policyTillDate = DateTime.now();
filteredPolicies = [];
});
context.go(AppRoutes.invoiceList);
} else {
filteredPolicies = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
// showDialog(
// context: context,
// builder: (_) => AlertDialog(
// title: const Text("Success"),
// content: Text(
// "Invoice No: ${invoiceNoController.text}\n"
// "Total Commission: ₹$totalComm",
// ),
// actions: [
// TextButton(
// onPressed: () => Navigator.pop(context),
// child: const Text("OK"),
// ),
// ],
// ),
// );
}
void showMessage(String msg) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
}
@override
Widget build(BuildContext context) {
final df = DateFormat("dd-MM-yyyy");
return MainLayout(
title: "Pay Out",
body: SelectionArea(
child: Stack(
children: [
SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// ----------------------
// Invoice Details
// ----------------------
Card(
color: Colors.white, // White background
clipBehavior: Clip.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 1,
child: Column(
children: [
// ---------- HEADER (CLICK TO COLLAPSE) ----------
ListTile(
title: const Text("Invoice Details"),
trailing: Icon(
invoiceCollapsed
? Icons.expand_more
: Icons.expand_less,
color: Color(0xFF2F766E),
size: 26,
),
onTap: () => setState(
() => invoiceCollapsed = !invoiceCollapsed,
),
),
// ------------------- HR LINE WHEN OPEN -------------------
if (!invoiceCollapsed)
const Divider(
height: 1,
thickness: 1,
color: Color(0xFFE0E0E0),
),
// ---------- COLLAPSIBLE CONTENT ----------
AnimatedCrossFade(
duration: const Duration(milliseconds: 500),
crossFadeState: invoiceCollapsed
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: const SizedBox(
// FIXED
height: 0,
width: double.infinity,
),
secondChild: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// ------------------- ROW OF FIELDS -------------------
SizedBox(
height: 80, // 👈 ensures bottom alignment works
child: Row(
crossAxisAlignment: CrossAxisAlignment
.center, // 👈 bottom align everything
children: [
// INVOICE NO
FormFieldBox(
label: "Invoice No *",
child: TextField(
controller: invoiceNoController,
readOnly: true,
decoration: commonInputDecoration(
hint: "Invoice Number",
),
),
),
const SizedBox(width: 12),
// INVOICE DATE
FormFieldBox(
label: "Invoice Date *",
child: GestureDetector(
onTap: () async {
final picked = await pickDate(
invoiceDate,
);
if (picked != null)
setState(
() => invoiceDate = picked,
);
},
child: AbsorbPointer(
child: TextField(
readOnly: true,
decoration:
commonInputDecoration(
hint: df.format(invoiceDate),
).copyWith(
suffixIcon: const Icon(
Icons.calendar_today,
size: 18,
),
),
),
),
),
),
const SizedBox(width: 12),
// BROKER
FormFieldBox(
label: "Select Broker *",
child: DropdownButtonFormField<int>(
value: selectedBrokerID,
decoration: commonInputDecoration(
hint: "Select Broker",
),
items: filteredBrokerData.map((a) {
final int id =
int.tryParse(
a['id'].toString(),
) ??
0;
return DropdownMenuItem<int>(
value: id,
child: Text(a['name'].toString()),
);
}).toList(),
onChanged: (id) => setState(
() => selectedBrokerID = id,
),
),
),
const SizedBox(width: 12),
// AGENT
FormFieldBox(
label: "Select Agent *",
child: DropdownButtonFormField<int>(
value: selectedAgentId,
decoration: commonInputDecoration(
hint: "Select Agent",
),
items: filteredAgentData.map((a) {
final int id =
int.tryParse(
a['id'].toString(),
) ??
0;
return DropdownMenuItem<int>(
value: id,
child: Text(a['name'].toString()),
);
}).toList(),
onChanged: (id) => setState(
() => selectedAgentId = id,
),
),
),
const SizedBox(width: 12),
// POLICY TILL DATE
FormFieldBox(
label: "Policy Till Date *",
child: GestureDetector(
onTap: () async {
final picked = await pickDate(
policyTillDate!,
);
if (picked != null)
setState(
() => policyTillDate = picked,
);
},
child: AbsorbPointer(
child: TextField(
readOnly: true,
decoration:
commonInputDecoration(
hint: df.format(
policyTillDate!,
),
).copyWith(
suffixIcon: const Icon(
Icons.calendar_today,
size: 18,
),
),
),
),
),
),
const SizedBox(width: 12),
// --- RESET + SEARCH (BOTTOM ALIGNED) ---
Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Row(
children: [
// RESET
SizedBox(
height: 30,
width: 30,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: () {
setState(() {
selectedBrokerID = null;
selectedAgentId = null;
fromDate = null;
toDate = null;
policyTillDate =
DateTime.now();
filteredPolicies = [];
selectedPolicies.clear();
});
},
child: const Icon(
Icons.refresh,
size: 18,
),
),
),
const SizedBox(width: 12),
// SEARCH
SizedBox(
height: 30,
width: 30,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: loadPolicies,
child: const Icon(
Icons.search,
size: 18,
),
),
),
],
),
],
),
],
),
),
],
),
),
),
],
),
),
const SizedBox(height: 20),
// ----------------------
// Policy Selection
// ----------------------
Card(
color: Colors.white, // White background
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 1,
child: Column(
children: [
ListTile(
// contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
title: const Text("Policy Selection"),
// ---------------- RIGHT SIDE (filters + collapse) ----------------
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
// FROM DATE
SizedBox(
height: 30,
width: MediaQuery.of(context).size.width * 0.12,
child: GestureDetector(
onTap: () async {
final date = await pickDate(
fromDate ?? DateTime.now(),
);
if (date != null)
setState(() => fromDate = date);
},
child: AbsorbPointer(
child: TextField(
readOnly: true,
decoration:
commonInputDecoration(
hint: "From Date",
).copyWith(
// 👇 THIS is where you set formatted date
suffixIcon: const Icon(
Icons.calendar_today,
size: 18,
),
labelText: fromDate == null
? "From Date"
: df.format(fromDate!),
),
),
),
),
),
const SizedBox(width: 8),
// TO DATE
SizedBox(
height: 30,
width: MediaQuery.of(context).size.width * 0.12,
child: GestureDetector(
onTap: () async {
final date = await pickDate(
toDate ?? DateTime.now(),
);
if (date != null)
setState(() => toDate = date);
},
child: AbsorbPointer(
child: TextField(
readOnly: true,
decoration:
commonInputDecoration(
hint: "To Date",
).copyWith(
// 👇 THIS is where you set formatted date
suffixIcon: const Icon(
Icons.calendar_today,
size: 18,
),
labelText: toDate == null
? "To Date"
: df.format(toDate!),
),
),
),
),
),
const SizedBox(width: 8),
// RESET BUTTON
SizedBox(
height: 30,
width: 30,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: () {
setState(() {
fromDate = null;
toDate = null;
});
loadPolicies();
},
child: const Icon(Icons.refresh, size: 18),
),
),
const SizedBox(width: 8),
// SEARCH BUTTON
SizedBox(
height: 30,
width: 30,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: filterPolicies,
child: const Icon(Icons.search, size: 18),
),
),
const SizedBox(width: 8),
// COLLAPSE ICON
GestureDetector(
onTap: () => setState(
() => policyCollapsed = !policyCollapsed,
),
child: Icon(
policyCollapsed
? Icons.expand_more
: Icons.expand_less,
color: Colors.teal,
size: 26,
),
),
],
),
onTap: () =>
setState(() => policyCollapsed = !policyCollapsed),
),
// ------------------- HR LINE WHEN OPEN -------------------
if (!policyCollapsed)
const Divider(
height: 1,
thickness: 1,
color: Color(0xFFE0E0E0),
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 500),
crossFadeState: policyCollapsed
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
firstChild: Container(),
secondChild: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// ---------------- SELECT ALL ----------------
if (filteredPolicies.isNotEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
child: Row(
children: [
Checkbox(
value:
filteredPolicies.isNotEmpty &&
filteredPolicies.every(
(p) => selectedPolicies.contains(
int.parse(p["policy_id"]),
),
),
onChanged: (v) {
setState(() {
if (v == true) {
selectedPolicies =
filteredPolicies
.map(
(p) => int.parse(
p["policy_id"],
),
)
.toSet();
} else {
selectedPolicies.clear();
}
});
},
),
const SizedBox(width: 8),
const Text(
"Select All Policies",
style: TextStyle(fontSize: 16),
),
],
),
),
// ---------------- POLICY LIST ----------------
if (filteredPolicies.isEmpty)
const Padding(
padding: EdgeInsets.all(20),
child: Text("No policies found"),
)
else
...filteredPolicies.map((p) {
// final int id = int.parse(p["policy_id"]);
// final bool isSelected = selectedPolicies.contains(id);
// ---- SAFE ID PARSING ----
final String policyIdStr =
p["policy_id"]?.toString() ?? "0";
final int id = int.tryParse(policyIdStr) ?? 0;
final bool isSelected = selectedPolicies
.contains(id);
// ---- SAFE FIELDS ----
final String policyNo =
p["policy_no"]?.toString() ?? "-";
final String customer =
p["customer_name"]?.toString() ?? "-";
final String premiumStr =
p["premium_amount"]?.toString() ?? "0";
final double premium =
double.tryParse(premiumStr) ?? 0;
final String commissionStr =
p["commission_amount"]?.toString() ?? "0";
final double commission =
double.tryParse(commissionStr) ?? 0;
return ListTile(
tileColor: isSelected
? Colors.teal.shade50
: null,
leading: Checkbox(
value: isSelected,
onChanged: (v) {
setState(() {
if (v == true) {
selectedPolicies.add(id);
} else {
selectedPolicies.remove(id);
}
});
},
),
title: Text(policyNo),
subtitle: Text(
"Customer: ${customer} | "
"Premium: ₹${premium}",
),
trailing: Text(
"${commission}",
style: const TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
),
),
onTap: () {
setState(() {
if (isSelected) {
selectedPolicies.remove(id);
} else {
selectedPolicies.add(id);
}
});
},
);
}).toList(),
],
),
),
),
],
),
),
const SizedBox(height: 100),
],
),
),
// ------------------------------
// Bottom Summary Bar
// ------------------------------
if (selectedPolicies.isNotEmpty)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Material(
elevation: 8,
child: Container(
padding: const EdgeInsets.all(16),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// ---------------- SELECTED COUNT ----------------
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Selected Policies",
style: TextStyle(fontSize: 12),
),
const SizedBox(height: 4),
Text(
selectedPolicies.length.toString(),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
],
),
// ---------------- TOTAL COMMISSION ----------------
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Total Commission",
style: TextStyle(fontSize: 12),
),
const SizedBox(height: 4),
/// NULL-SAFE TOTAL COMMISSION CALCULATION
Text(
"${_calculateTotalCommission()}",
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.teal,
),
),
],
),
// ---------------- BUTTONS ----------------
Row(
children: [
TextButton(
onPressed: () =>
setState(() => selectedPolicies.clear()),
child: const Text("Clear"),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: saveInvoice,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
),
child: const Text("Submit"),
),
],
),
],
),
),
),
),
],
), ),
);
}
double _calculateTotalCommission() {
double total = 0.0;
for (var p in filteredPolicies) {
// Safe policy_id parsing
final String idStr = p["policy_id"]?.toString() ?? "0";
final int id = int.tryParse(idStr) ?? 0;
if (!selectedPolicies.contains(id)) continue;
// Safe commission amount
final String commStr = p["commission_amount"]?.toString() ?? "0";
final double comm = double.tryParse(commStr) ?? 0;
total += comm;
}
// Remove decimal points (.00)
return total.roundToDouble();
}
InputDecoration commonInputDecoration({required String hint}) {
return InputDecoration(
hintText: hint,
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: Color(0xFFDDDDDD), // light gray
width: 1,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: const BorderSide(
color: Color(0xFFAAAAAA), // darker gray on focus
width: 1.2,
),
),
);
}
}