nhance_partner/lib/presentation/screens/staff/Enquiry/quotationPopUpAccptReject.dart
2026-01-08 16:01:06 +05:30

584 lines
18 KiB
Dart

import 'dart:convert';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
import '../../../../../core/config/env.dart';
import '../../../../../core/services/api_service.dart';
import '../../../../../data/services/auth_service.dart';
import '../../../../../data/utils/toastNotification.dart';
import '../../../layouts/responsive_layout.dart';
import '../../../providers/manager_provider.dart';
class QuotationPopUpTab extends ConsumerStatefulWidget {
String? id;
final Future<void> Function()? onRefresh;
QuotationPopUpTab({super.key, this.id, this.onRefresh});
@override
ConsumerState<QuotationPopUpTab> createState() => QuotationTabState();
}
class QuotationTabState extends ConsumerState<QuotationPopUpTab> {
late ApiService apiService;
bool isLoading = false;
bool blockKey = false;
String? _token;
dynamic userId;
dynamic managerId;
dynamic role;
List<Map<String, dynamic>> getQuotationData = [];
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
@override
void initState() {
super.initState();
apiService = ApiService();
_initializeToken();
Future.microtask(() {
managerId = ref.watch(managerIdProvider);
userId = ref.watch(userIdProvider);
role = ref.watch(userRoleProvider);
getQuotationList();
});
}
Future<void> _initializeToken() async {
_token = await AuthService.getToken();
print("APISERTOKEN - $_token");
}
void refresh() {
getQuotationList();
}
/// ✅ Instead of using `widget.data`, call the API directly
Future<void> getQuotationList() async {
print('Fetching quotation list from API...');
setState(() => isLoading = true);
try {
if (widget.id == null) {
throw Exception('QuotationTab: ID is null');
}
// Example: adjust this endpoint based on your actual API
final response = await apiService.findEnqQuotePolicyView(widget.id);
// Extract quotation data safely
final quotations = (response["data"]?["quotations"] as List?) ?? [];
final data = quotations
.map((e) => Map<String, dynamic>.from(e as Map))
.toList();
setState(() {
getQuotationData = data;
filteredData = List.from(data);
blockKey = data.any((item) => item['status'] == 'Accepted');
});
print('Fetched quotation data: ${data.length}');
} catch (e, s) {
print('❌ Error fetching quotations: $e\n$s');
} finally {
setState(() => isLoading = false);
}
}
Future<void> handleAction(String action, String quotationId) async {
final String apiUrldata = '${Env.apiUrl}quotation/acceptOrRejectQuotation';
if (_token == null) {
throw Exception('Token not found. Please log in.');
}
final Map<String, dynamic> data = {
"id": quotationId,
"status": action,
"action_by": userId,
"action_user": role,
};
print("data------- $data");
try {
final response = await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $_token',
'Content-Type': 'application/json',
'app-signature': Env.App_Signature,
},
body: jsonEncode(data),
);
if (response.statusCode == 200) {
print("Response: ${response.body}");
ToastHelper.showSuccessToast(context, 'Status Updated');
// Close popup
Navigator.of(context).pop();
// Refresh parent
if (widget.onRefresh != null) {
await widget.onRefresh!();
}
} else if (response.statusCode == 403) {
await apiService.clearLocalStorageAndRedirect();
} else {
final responseBody = jsonDecode(response.body);
ToastHelper.showErrorToast(context, 'Status Updation Failed');
print("Failed to submit. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print("Error submitting: $e");
}
}
Widget _buildQuotationPopupContent() {
return Container(
// width: ResponsiveLayout.isMobile(context)
// ? MediaQuery.of(context).size.width * 0.9
// : MediaQuery.of(context).size.width * 0.5,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
// border: Border.all(color: const Color(0xFFD9EBE8)),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.05),
// blurRadius: 8,
// offset: const Offset(0, 4),
// ),
// ],
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Proposal Details',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: Icon(Icons.close),
),
],
),
const SizedBox(height: 5),
Flexible(
child: SingleChildScrollView(
child: _buildQuotationTable(), // ✅ Only one table
),
),
],
),
);
}
Widget _buildQuotationTable() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFD9EBE8)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🔹 Table Header
Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
color: const Color(0xFFEFF6F5),
child: Row(
children: const [
Expanded(
flex: 1,
child: Text(
"IDV",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 2,
child: Text(
"Plan Type",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 1,
child: Text(
"Premium",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 3,
child: Center(
child: Text(
"Action",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
],
),
),
const Divider(height: 1, color: Color(0xFFD9EBE8)),
// 🔹 Table Rows
...filteredData.map((item) {
final bool isAccepted = item['status'] == 'Accepted';
final bool isRejected = item['status'] == 'Rejected';
final bool isPending = item['status'] == 'Pending';
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.grey.withOpacity(0.2)),
),
color: isAccepted
? Colors.green.withOpacity(0.05)
: isRejected
? Colors.red.withOpacity(0.05)
: Colors.white,
),
child: Row(
children: [
Expanded(
flex: 1,
child: Text(
item['insured_declared_value']?.toString() ?? '-',
style: const TextStyle(fontSize: 14),
),
),
Expanded(
flex: 2,
child: Text(
item['insurance_plan_type']?.toString() ?? '-',
style: const TextStyle(fontSize: 14),
),
),
Expanded(
flex: 1,
child: Text(
item['premium_amount']?.toString() ?? '-',
style: const TextStyle(fontSize: 14),
),
),
Expanded(
flex: 3,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (isPending && !blockKey) ...[
TextButton(
onPressed: () =>
handleAction('Rejected', item['id']),
style: TextButton.styleFrom(
backgroundColor: Colors.red.shade50,
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
),
child: const Text(
'Reject',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () =>
handleAction('Accepted', item['id']),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF425B5B),
padding: const EdgeInsets.symmetric(
horizontal: 12,
),
),
child: const Text(
'Accept',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
],
if (isAccepted)
const Text(
'Accepted',
style: TextStyle(
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
if (isRejected)
const Text(
'Rejected',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
],
),
),
],
),
);
}),
],
),
);
}
Widget _buildQuotationCard(Map<String, dynamic> item) {
final bool isAccepted = item['status'] == 'Accepted';
final bool isRejected = item['status'] == 'Rejected';
final bool isPending = item['status'] == 'Pending';
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isAccepted
? Colors.green.withOpacity(0.1)
: isRejected
? Colors.red.withOpacity(0.1)
: const Color(0xFFF6FEFD),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isAccepted
? Colors.green
: isRejected
? Colors.red
: const Color(0xffD9EBE8),
width: 1.5,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item['insurer_name'] ?? '-',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: _getStatusColor(item['status']),
borderRadius: BorderRadius.circular(12),
),
child: Text(
item['status'] ?? '-',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
],
),
const SizedBox(height: 12),
// Details
Row(
children: [
Expanded(
child: _buildDetailItem(
'IDV',
item['insured_declared_value']?.toString() ?? '-',
),
),
Expanded(
child: _buildDetailItem(
'Premium',
item['premium_amount']?.toString() ?? '-',
),
),
],
),
const SizedBox(height: 8),
_buildDetailItem(
'Plan Type',
item['insurance_plan_type']?.toString() ?? '-',
),
// Action buttons (only if pending and no other quotation is accepted)
if (isPending && !blockKey) ...[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => handleAction('Rejected', item['id']),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
backgroundColor: Colors.red.shade50,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Reject',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 12),
ElevatedButton(
onPressed: () => handleAction('Accepted', item['id']),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
backgroundColor: const Color(0xFF425B5B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text(
'Accept',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
],
),
],
// Show message if blocked
if (isPending && blockKey) ...[
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(Icons.info_outline, size: 16, color: Colors.orange),
SizedBox(width: 8),
Expanded(
child: Text(
'Another quotation has been accepted',
style: TextStyle(fontSize: 12, color: Colors.orange),
),
),
],
),
),
],
],
),
);
}
Widget _buildDetailItem(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label: ',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF545454),
),
),
Expanded(
child: Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
),
],
),
);
}
Color _getStatusColor(String? status) {
switch (status) {
case 'Pending':
return Colors.orange;
case 'Accepted':
return Colors.green;
case 'Rejected':
return Colors.red;
default:
return Colors.grey;
}
}
@override
@override
Widget build(BuildContext context) {
return SelectionArea(
child: Container(
// height: MediaQuery.of(context).size.height,
// width: MediaQuery.of(context).size.width,
// padding: const EdgeInsets.all(16),
child: isLoading
? const Center(child: CircularProgressIndicator())
: filteredData.isNotEmpty
? SingleChildScrollView(
child: Column(
children: [
_buildQuotationPopupContent(), // ✅ now renders inline
const SizedBox(height: 20),
],
),
)
: const Center(child: Text('No Available Data')),
),);
}
}