fix_odc issues
This commit is contained in:
parent
722d8e4904
commit
50a1efb695
File diff suppressed because one or more lines are too long
@ -5,8 +5,8 @@ class Env {
|
||||
);
|
||||
static const String apiUrl = String.fromEnvironment(
|
||||
'API_URL',
|
||||
defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */
|
||||
// defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */
|
||||
// defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */
|
||||
defaultValue: 'https://venbait.in/nhance/partner/dev/api/', /* Test build (enable index.html line 19) */
|
||||
// defaultValue: 'http://localhost/nhance_partner_be/', /* localhost build (enable index.html line 19) */
|
||||
);
|
||||
// static const String baseUrl = String.fromEnvironment(
|
||||
|
||||
@ -2269,4 +2269,92 @@ class ApiService {
|
||||
//
|
||||
// return response;
|
||||
// }
|
||||
|
||||
Future<Map<String, dynamic>> deleteEndorsement(id) async {
|
||||
|
||||
if (_token == null) {
|
||||
await _initializeToken();
|
||||
}
|
||||
final url = Uri.parse('${Env.apiUrl}endorsement/deleteEndorsement?id=$id');
|
||||
// final url = Uri.parse('http://localhost/nhance_partner_be/endorsement/deleteEndorsement?id=$id');
|
||||
|
||||
final headers = {
|
||||
'Authorization': 'Bearer $_token' ?? '',
|
||||
'app-signature': Env.App_Signature,
|
||||
};
|
||||
final response = await _makeGetRequest(url, headers);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
Future<Map<String, dynamic>> uploadEndorsementFile({
|
||||
required PlatformFile file,
|
||||
required Map<String, dynamic> data,
|
||||
}) async {
|
||||
if (_token == null) {
|
||||
await _initializeToken();
|
||||
}
|
||||
|
||||
final url = Uri.parse('${Env.apiUrl}endorsement/uploadEndorsementFile');
|
||||
// final url = Uri.parse('http://localhost/nhance_partner_be/endorsement/uploadEndorsementFile');
|
||||
|
||||
try {
|
||||
final request = http.MultipartRequest('POST', url);
|
||||
|
||||
request.headers.addAll({
|
||||
'Authorization': 'Bearer $_token',
|
||||
'app-signature': Env.App_Signature,
|
||||
});
|
||||
|
||||
if (file.bytes == null) throw Exception('Could not read file bytes');
|
||||
|
||||
request.files.add(
|
||||
http.MultipartFile.fromBytes(
|
||||
'endorsement_completion_file',
|
||||
file.bytes!,
|
||||
filename: file.name,
|
||||
),
|
||||
);
|
||||
|
||||
data.forEach((key, value) {
|
||||
if (value != null) {
|
||||
request.fields[key] = value.toString();
|
||||
}
|
||||
});
|
||||
|
||||
print('uploadEndorsementFile url => $url');
|
||||
print('uploadEndorsementFile fields => ${request.fields}');
|
||||
print('uploadEndorsementFile fileName => ${file.name}');
|
||||
|
||||
final streamedResponse = await request.send();
|
||||
final responseBody = await streamedResponse.stream.bytesToString();
|
||||
|
||||
print('uploadEndorsementFile raw response => $responseBody');
|
||||
|
||||
if (streamedResponse.statusCode == 401 ||
|
||||
streamedResponse.statusCode == 403) {
|
||||
await clearLocalStorageAndRedirect();
|
||||
return {'status': 'error', 'message': 'Session expired'};
|
||||
}
|
||||
|
||||
if (streamedResponse.statusCode != 200) {
|
||||
return {
|
||||
'status': 'error',
|
||||
'message': 'Server Error: ${streamedResponse.statusCode}',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
final decoded = jsonDecode(responseBody);
|
||||
return decoded is Map<String, dynamic>
|
||||
? decoded
|
||||
: {'status': 'error', 'message': 'Unexpected response format'};
|
||||
} catch (_) {
|
||||
return {'status': 'error', 'message': 'Failed to parse response'};
|
||||
}
|
||||
} catch (e) {
|
||||
print('uploadEndorsementFile exception => $e');
|
||||
return {'status': 'error', 'message': e.toString()};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:dropdown_search/dropdown_search.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@ -9,9 +10,11 @@ import 'package:nhance_partner/presentation/providers/userRoleProvider.dart';
|
||||
import '../../../../core/routing/routes.dart';
|
||||
import '../../../../core/services/api_service.dart';
|
||||
import '../../../../data/utils/Pagination.dart';
|
||||
import '../../../../data/utils/toastNotification.dart';
|
||||
import '../../../layouts/main_layout.dart';
|
||||
import '../../../layouts/responsive_layout.dart';
|
||||
import '../../../providers/manager_provider.dart';
|
||||
import '../../../themes/indicators/customizd_file_upload.dart';
|
||||
import '../../../themes/indicators/date_field_theme.dart';
|
||||
import '../../../themes/indicators/export_btn.dart';
|
||||
import '../../../themes/indicators/input_field_decoration.dart';
|
||||
@ -49,6 +52,10 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
List<Map<String, dynamic>> filteredEndrosmentData = [];
|
||||
String? hoveredRowId;
|
||||
|
||||
String? selectedFileNames;
|
||||
PlatformFile? docUploadedFile;
|
||||
String? lastPickedFile;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -208,6 +215,103 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
});
|
||||
}
|
||||
|
||||
void _confirmDelete(BuildContext context, String id) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(Icons.warning_amber_rounded, color: Colors.red.shade600, size: 22),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Delete Endorsement',
|
||||
style: GoogleFonts.poppins(fontSize: 15, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
content: Text(
|
||||
'Are you sure you want to delete this endorsement?\nThis action cannot be undone.',
|
||||
style: GoogleFonts.poppins(fontSize: 13, color: Colors.grey.shade700),
|
||||
),
|
||||
actions: [
|
||||
// CANCEL
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx),
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: GoogleFonts.poppins(fontSize: 13, color: Colors.grey.shade600),
|
||||
),
|
||||
),
|
||||
|
||||
// CONFIRM DELETE
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red.shade600,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
onPressed: () async {
|
||||
Navigator.pop(ctx);
|
||||
await _deleteEndorsement(id);
|
||||
},
|
||||
child: Text(
|
||||
'Delete',
|
||||
style: GoogleFonts.poppins(fontSize: 13, color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _deleteEndorsement(String id) async {
|
||||
try {
|
||||
setState(() => isLoading = true);
|
||||
|
||||
final response = await apiService.deleteEndorsement(id);
|
||||
|
||||
if (response['status'] == 'success' || response['code'] == 200) {
|
||||
refresh();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Endorsement deleted successfully.',
|
||||
style: GoogleFonts.poppins(fontSize: 13),
|
||||
),
|
||||
backgroundColor: Colors.green.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
response['message'] ?? 'Failed to delete endorsement.',
|
||||
style: GoogleFonts.poppins(fontSize: 13),
|
||||
),
|
||||
backgroundColor: Colors.red.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error: $e', style: GoogleFonts.poppins(fontSize: 13)),
|
||||
backgroundColor: Colors.red.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
setState(() => isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
final TextEditingController rightSearchController = TextEditingController();
|
||||
final TextEditingController startController = TextEditingController();
|
||||
final TextEditingController endController = TextEditingController();
|
||||
@ -292,6 +396,27 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// --- DELETE BUTTON ---
|
||||
if (roleId == 'Accounts') ...[
|
||||
const Divider(height: 1, thickness: 0.5),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(context);
|
||||
_confirmDelete(context, Id);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 4.0),
|
||||
child: Row(
|
||||
children: const [
|
||||
Icon(Icons.delete_outline, color: Colors.red, size: 18),
|
||||
SizedBox(width: 12),
|
||||
Text('Delete', style: TextStyle(fontSize: 14, color: Colors.red)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -548,6 +673,13 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
style: _headerStyle,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
'Remarks',
|
||||
style: _headerStyle,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
@ -563,6 +695,10 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
style: _headerStyle,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text('Pending Days', style: _headerStyle),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text('Action', style: _headerStyle),
|
||||
@ -840,7 +976,17 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
flex: 2,
|
||||
child:
|
||||
Text(item['broker_name'] ?? '-', style: _dataBold)),
|
||||
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
item['endorsement_description'] != null && item['endorsement_description'].toString().isNotEmpty
|
||||
? item['endorsement_description'].toString()
|
||||
: '-',
|
||||
style: _dataBold,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _buildBadge(
|
||||
@ -858,21 +1004,35 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
type: 'verification',
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final days = _calculatePendingDays(item['created_at']?.toString());
|
||||
return Center(
|
||||
child: Text(
|
||||
item['status'] == 'Closed' ? '-' : '$days',
|
||||
textAlign: TextAlign.center,
|
||||
style: _dataBold.copyWith(
|
||||
color: days > 7 ? Colors.red : days > 3 ? Colors.orange : Colors.green,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: item['is_active'] == "1"
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
constraints: const BoxConstraints(),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Image.asset(
|
||||
"assets/miscellaneous/Edit.png",
|
||||
height: 15,
|
||||
width: 15),
|
||||
onPressed: () async {
|
||||
// --- EDIT ---
|
||||
_actionIconButton(
|
||||
context: context,
|
||||
tooltip: 'Edit',
|
||||
iconColor: const Color(0xFF319718),
|
||||
onTap: () async {
|
||||
final result = await context.push(
|
||||
AppRoutes.endorsomentValidation,
|
||||
extra: {
|
||||
@ -883,33 +1043,71 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
"role": roleId,
|
||||
},
|
||||
);
|
||||
if (result == true) {
|
||||
refresh(); // 👈 reload list API
|
||||
}
|
||||
if (result == true) refresh();
|
||||
},
|
||||
customIcon: Image.asset(
|
||||
"assets/miscellaneous/Edit.png",
|
||||
height: 15,
|
||||
width: 15,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
InkWell(
|
||||
onTap: () =>
|
||||
apiService.downloadFile(
|
||||
),
|
||||
|
||||
// --- UPLOAD ---
|
||||
_actionIconButton(
|
||||
context: context,
|
||||
tooltip: 'Upload',
|
||||
iconColor: Colors.green.shade600,
|
||||
hoverColor: Colors.green.shade50,
|
||||
onTap: null,
|
||||
builderIcon: (buttonContext) => Material(
|
||||
color: Colors.transparent,
|
||||
child: Tooltip(
|
||||
message: 'Upload PDF',
|
||||
waitDuration: const Duration(milliseconds: 300),
|
||||
showDuration: const Duration(seconds: 2),
|
||||
child: InkWell(
|
||||
onTap: () => _showEndorsementUploadMenu(buttonContext, item),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
hoverColor: Colors.grey.shade200,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: Icon(
|
||||
Icons.file_upload_outlined,
|
||||
size: 15,
|
||||
color: Colors.green,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// --- DOWNLOAD ---
|
||||
_actionIconButton(
|
||||
context: context,
|
||||
icon: Icons.download_rounded,
|
||||
tooltip: 'Download',
|
||||
iconColor: Colors.blue.shade600,
|
||||
hoverColor: Colors.blue.shade50, // ✅ red tint on hover for delete
|
||||
onTap: () => apiService.downloadFile(
|
||||
apiUrl:
|
||||
'endorsement/downloadEndorsementCompletionFile?id=$id',
|
||||
apiId: id,
|
||||
localFile: null,
|
||||
fileName: fileName,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.circular(4),
|
||||
color: Colors.green,
|
||||
),
|
||||
child: const Icon(Icons.download,
|
||||
size: 14,
|
||||
color: Colors.white),
|
||||
),
|
||||
),
|
||||
|
||||
// --- DELETE (Accounts only) ---
|
||||
if (roleId == 'Accounts')
|
||||
_actionIconButton(
|
||||
context: context,
|
||||
icon: Icons.delete_outline_rounded,
|
||||
tooltip: 'Delete',
|
||||
iconColor: Colors.red.shade600,
|
||||
hoverColor: Colors.red.shade50, // ✅ red tint on hover for delete
|
||||
onTap: () => _confirmDelete(context, id),
|
||||
)
|
||||
],
|
||||
)
|
||||
: const Text('-'),
|
||||
@ -1073,6 +1271,39 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Remarks", style: _cardheaderStyle),
|
||||
Text(
|
||||
item['endorsement_description'] != null && item['endorsement_description'].toString().isNotEmpty
|
||||
? item['endorsement_description'].toString()
|
||||
: '-',
|
||||
style: _cardBodyStyle,
|
||||
maxLines: 3,
|
||||
softWrap: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Pending Days", style: _cardheaderStyle),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final days = _calculatePendingDays(item['created_at']?.toString());
|
||||
return Text(
|
||||
item['status'] == 'Closed' ? '-' : '$days days',
|
||||
style: _cardBodyStyle.copyWith(
|
||||
color: days > 7 ? Colors.red : days > 3 ? Colors.orange : Colors.green,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -1786,6 +2017,39 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionIconButton({
|
||||
required BuildContext context,
|
||||
IconData? icon,
|
||||
Widget? customIcon,
|
||||
Widget Function(BuildContext buttonContext)? builderIcon,
|
||||
required String tooltip,
|
||||
required Color iconColor,
|
||||
required VoidCallback? onTap,
|
||||
Color? hoverColor, // ✅ Add this parameter
|
||||
}) {
|
||||
if (builderIcon != null) {
|
||||
return Builder(builder: (buttonContext) => builderIcon(buttonContext));
|
||||
}
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Tooltip(
|
||||
message: tooltip,
|
||||
waitDuration: const Duration(milliseconds: 300),
|
||||
showDuration: const Duration(seconds: 2),
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
hoverColor: hoverColor ?? Colors.grey.shade200, // ✅ default grey
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: customIcon ?? Icon(icon, size: 15, color: iconColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget _editButton(item) {
|
||||
// return InkWell(
|
||||
// onTap: () async {
|
||||
@ -1912,6 +2176,274 @@ class endosementState extends ConsumerState<Endorsement> {
|
||||
);
|
||||
}
|
||||
|
||||
int _calculatePendingDays(String? createdAt) {
|
||||
if (createdAt == null || createdAt.isEmpty) return 0;
|
||||
try {
|
||||
final created = DateFormat('dd-MM-yyyy').parse(createdAt.split(' ')[0]);
|
||||
final today = DateTime.now();
|
||||
return today.difference(created).inDays;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showEndorsementUploadMenu(
|
||||
BuildContext buttonContext,
|
||||
Map<String, dynamic> item,
|
||||
) async {
|
||||
final button = buttonContext.findRenderObject() as RenderBox;
|
||||
final overlay =
|
||||
Overlay.of(buttonContext).context.findRenderObject() as RenderBox;
|
||||
final position = button.localToGlobal(Offset.zero, ancestor: overlay);
|
||||
|
||||
await showMenu(
|
||||
context: buttonContext,
|
||||
position: RelativeRect.fromLTRB(
|
||||
position.dx,
|
||||
position.dy + button.size.height,
|
||||
overlay.size.width,
|
||||
0,
|
||||
),
|
||||
items: [
|
||||
PopupMenuItem(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Upload Endorsement PDF', style: _dataBold),
|
||||
const SizedBox(height: 10),
|
||||
_buildEndorsementUploadField(item),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
color: Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
// Helper: Endorsement upload field
|
||||
Widget _buildEndorsementUploadField(Map<String, dynamic> item) {
|
||||
final String? uploadedFile = item['endorsement_completion_file'];
|
||||
final String? uploadedFileName = (uploadedFile != null && uploadedFile.isNotEmpty)
|
||||
? uploadedFile.split('/').last
|
||||
: null;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// ── UPLOAD LABEL ──────────────────────────────
|
||||
Text('Upload', style: GoogleFonts.poppins(fontSize: 11, fontWeight: FontWeight.w500)),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
// ── UPLOAD FIELD ──────────────────────────────
|
||||
ThemedUploadField(
|
||||
hintText: selectedFileNames ?? "Upload Document",
|
||||
padHorizontal: 4,
|
||||
padVertical: 5,
|
||||
fontSZ: 11,
|
||||
borderCirculr: 5,
|
||||
txtwidth: ResponsiveLayout.isMobile(context)
|
||||
? null
|
||||
: MediaQuery.of(context).size.width * 0.18,
|
||||
borderColor: const Color(0xFFE2E8F0),
|
||||
onFileSelected: (fileName, file) =>
|
||||
_handleEndorsementFileUpload(fileName, file, item),
|
||||
),
|
||||
|
||||
const SizedBox(height: 10),
|
||||
|
||||
// ── ALREADY UPLOADED FILE ROW ─────────────────
|
||||
if (uploadedFileName != null) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF0FDF4),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: const Color(0xFFBBF7D0)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.picture_as_pdf, color: Colors.red, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
uploadedFileName,
|
||||
style: GoogleFonts.poppins(fontSize: 11, color: Colors.black87),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
// ── DOWNLOAD BUTTON ──────────────────
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
apiService.downloadFile(
|
||||
apiUrl: 'endorsement/downloadEndorsementCompletionFile?id=${item['id']}&type=completion',
|
||||
apiId: item['id'].toString(),
|
||||
localFile: null,
|
||||
fileName: uploadedFileName,
|
||||
);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
hoverColor: Colors.blue.shade50, // ✅ hover color
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Icon(
|
||||
Icons.download_rounded,
|
||||
color: Colors.blue, // ✅ icon color
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
// ── NO FILE MESSAGE ───────────────────────
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFF7ED),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: const Color(0xFFFED7AA)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.info_outline, color: Colors.orange.shade400, size: 14),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'No file uploaded yet',
|
||||
style: GoogleFonts.poppins(fontSize: 11, color: Colors.orange.shade700),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Helper: Handle Endorsement file upload
|
||||
Future<void> _handleEndorsementFileUpload(
|
||||
String? fileName,
|
||||
PlatformFile? file,
|
||||
Map<String, dynamic> item,
|
||||
) async {
|
||||
|
||||
// ✅ Guard: userId must exist
|
||||
if (userId == null) {
|
||||
ToastHelper.showErrorToast(context, 'User session not found. Please re-login.');
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Guard: item id must exist
|
||||
if (item["id"] == null) {
|
||||
ToastHelper.showErrorToast(context, 'Invalid endorsement record.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastPickedFile == fileName) {
|
||||
ToastHelper.showErrorToast(context, 'Please upload a new file.');
|
||||
setState(() {
|
||||
selectedFileNames = null;
|
||||
docUploadedFile = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (file == null || fileName == null || fileName.isEmpty || file.size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileName.toLowerCase().endsWith('.pdf')) {
|
||||
ToastHelper.showErrorToast(context, 'Only PDF files are allowed.');
|
||||
return;
|
||||
}
|
||||
|
||||
lastPickedFile = fileName;
|
||||
setState(() {
|
||||
selectedFileNames = fileName;
|
||||
docUploadedFile = file;
|
||||
});
|
||||
|
||||
await uploadEndorsementPDF(
|
||||
file: file, // ✅ use local var, not state var
|
||||
data: {
|
||||
"updated_by": userId,
|
||||
"id": item["id"],
|
||||
},
|
||||
);
|
||||
|
||||
if (context.mounted) Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
Future<void> uploadEndorsementPDF({
|
||||
PlatformFile? file,
|
||||
required Map<String, dynamic> data,
|
||||
}) async {
|
||||
if (file == null) return;
|
||||
|
||||
try {
|
||||
setState(() => isLoading = true);
|
||||
|
||||
final response = await apiService.uploadEndorsementFile(
|
||||
file: file,
|
||||
data: data,
|
||||
);
|
||||
|
||||
if (response['status'] == 'success' || response['code'] == 200) {
|
||||
setState(() {
|
||||
selectedFileNames = null;
|
||||
docUploadedFile = null;
|
||||
lastPickedFile = null;
|
||||
});
|
||||
refresh();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'File uploaded successfully.',
|
||||
style: GoogleFonts.poppins(fontSize: 13),
|
||||
),
|
||||
backgroundColor: Colors.green.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
response['message'] ?? 'Upload failed.',
|
||||
style: GoogleFonts.poppins(fontSize: 13),
|
||||
),
|
||||
backgroundColor: Colors.red.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error: $e', style: GoogleFonts.poppins(fontSize: 13)),
|
||||
backgroundColor: Colors.red.shade600,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
setState(() => isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final _dataBold = GoogleFonts.inter(
|
||||
fontSize: 11,
|
||||
|
||||
@ -70,9 +70,10 @@ class policylistState extends ConsumerState<policylist> {
|
||||
roleId = ref.read(userRoleProvider);
|
||||
userId = ref.read(userIdProvider);
|
||||
|
||||
print("D49 => r : $roleId | mId: $managerId | uId: $userId ");
|
||||
SelectedStatus = (roleId == 'Accounts') ? 'Not Verified' : 'All';
|
||||
print("D74 => r : $roleId | mId: $managerId | uId: $userId ]'-'[ SS : $SelectedStatus ");
|
||||
if (userId != null) {
|
||||
getStaffList(managerId!, userId, roleId , selectedStatus : SelectedStatus ?? 'All');
|
||||
getStaffList(managerId!, userId, roleId , selectedStatus : SelectedStatus);
|
||||
}
|
||||
|
||||
refreshSub = ref.listenManual<bool>(policyDataAcurancyRefreshProvider, (
|
||||
@ -83,7 +84,8 @@ class policylistState extends ConsumerState<policylist> {
|
||||
print('refresh triggered Quick Creation');
|
||||
|
||||
// callRefresh();
|
||||
getStaffList(managerId!, userId, roleId,selectedStatus : SelectedStatus ?? 'All');
|
||||
print("D87 => r : $roleId | mId: $managerId | uId: $userId ]'-'[ SS : $SelectedStatus ");
|
||||
getStaffList(managerId!, userId, roleId,selectedStatus : SelectedStatus);
|
||||
ref.read(policyDataAcurancyRefreshProvider.notifier).state = false;
|
||||
}
|
||||
});
|
||||
@ -92,12 +94,13 @@ class policylistState extends ConsumerState<policylist> {
|
||||
|
||||
void refresh() {
|
||||
managerId = ref.read(managerIdProvider);
|
||||
final roleId = ref.read(userRoleProvider);
|
||||
roleId = ref.read(userRoleProvider);
|
||||
final userId = ref.read(userIdProvider);
|
||||
|
||||
// print("D61 => r : $roleId | mId: $id | uId: $userId");
|
||||
SelectedStatus = (roleId == 'Accounts') ? 'Not Verified' : 'All';
|
||||
print("D100 => r : $roleId | mId: $managerId | uId: $userId ]'-'[ SS : $SelectedStatus ");
|
||||
if (userId != null) {
|
||||
getStaffList(managerId, userId, roleId,selectedStatus : SelectedStatus ?? 'All');
|
||||
getStaffList(managerId, userId, roleId,selectedStatus : SelectedStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,7 +247,7 @@ class policylistState extends ConsumerState<policylist> {
|
||||
SelectedStaffId = null;
|
||||
controllers['startDate']!.clear();
|
||||
controllers['endDate']!.clear();
|
||||
SelectedStatus ='All';
|
||||
SelectedStatus = (roleId == 'Accounts') ? 'Not Verified' : 'All'; // ✅
|
||||
SelectedInsurer = '';
|
||||
controllers['startDate']?.text = '';
|
||||
controllers['endDate']?.text = '';
|
||||
|
||||
@ -534,13 +534,17 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
||||
final List<Map<String, dynamic>> policyReportStatusOptions;
|
||||
|
||||
policyReportStatusOptions = [
|
||||
{'id': 'All', 'status': 'All'},
|
||||
{'id': 'Verified', 'status': 'Verified'},
|
||||
{'id': 'Not Verified', 'status': 'Not Verified'},
|
||||
{'id': 'Verified', 'status': 'Verified'},
|
||||
{'id': 'All', 'status': 'All'},
|
||||
];
|
||||
|
||||
// ✅ If selectedStatusVal is null, fall back based on role
|
||||
final String effectiveStatus = widget.selectedStatusVal ??
|
||||
(widget.role == 'Accounts' ? 'Not Verified' : 'All');
|
||||
|
||||
Map<String, dynamic>? selectedPolicyReportStatusMap = policyReportStatusOptions
|
||||
.where((element) => element['status'] == widget.selectedStatusVal)
|
||||
.where((e) => e['status'] == effectiveStatus)
|
||||
.cast<Map<String, dynamic>>()
|
||||
.toList()
|
||||
.firstOrNull;
|
||||
|
||||
22
pubspec.lock
22
pubspec.lock
@ -117,10 +117,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
version: "1.4.1"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -780,26 +780,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1297,10 +1297,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6"
|
||||
version: "0.7.10"
|
||||
toastification:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1502,5 +1502,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.8.1 <4.0.0"
|
||||
dart: ">=3.9.0-0 <4.0.0"
|
||||
flutter: ">=3.32.0"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user