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(
|
static const String apiUrl = String.fromEnvironment(
|
||||||
'API_URL',
|
'API_URL',
|
||||||
defaultValue: 'https://partner.nhanceindia.in/partner_api/api/', /* Live build (enable index.html line 18) */
|
// 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://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) */
|
// defaultValue: 'http://localhost/nhance_partner_be/', /* localhost build (enable index.html line 19) */
|
||||||
);
|
);
|
||||||
// static const String baseUrl = String.fromEnvironment(
|
// static const String baseUrl = String.fromEnvironment(
|
||||||
|
|||||||
@ -2269,4 +2269,92 @@ class ApiService {
|
|||||||
//
|
//
|
||||||
// return response;
|
// 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:dropdown_search/dropdown_search.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.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/routing/routes.dart';
|
||||||
import '../../../../core/services/api_service.dart';
|
import '../../../../core/services/api_service.dart';
|
||||||
import '../../../../data/utils/Pagination.dart';
|
import '../../../../data/utils/Pagination.dart';
|
||||||
|
import '../../../../data/utils/toastNotification.dart';
|
||||||
import '../../../layouts/main_layout.dart';
|
import '../../../layouts/main_layout.dart';
|
||||||
import '../../../layouts/responsive_layout.dart';
|
import '../../../layouts/responsive_layout.dart';
|
||||||
import '../../../providers/manager_provider.dart';
|
import '../../../providers/manager_provider.dart';
|
||||||
|
import '../../../themes/indicators/customizd_file_upload.dart';
|
||||||
import '../../../themes/indicators/date_field_theme.dart';
|
import '../../../themes/indicators/date_field_theme.dart';
|
||||||
import '../../../themes/indicators/export_btn.dart';
|
import '../../../themes/indicators/export_btn.dart';
|
||||||
import '../../../themes/indicators/input_field_decoration.dart';
|
import '../../../themes/indicators/input_field_decoration.dart';
|
||||||
@ -49,6 +52,10 @@ class endosementState extends ConsumerState<Endorsement> {
|
|||||||
List<Map<String, dynamic>> filteredEndrosmentData = [];
|
List<Map<String, dynamic>> filteredEndrosmentData = [];
|
||||||
String? hoveredRowId;
|
String? hoveredRowId;
|
||||||
|
|
||||||
|
String? selectedFileNames;
|
||||||
|
PlatformFile? docUploadedFile;
|
||||||
|
String? lastPickedFile;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
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 rightSearchController = TextEditingController();
|
||||||
final TextEditingController startController = TextEditingController();
|
final TextEditingController startController = TextEditingController();
|
||||||
final TextEditingController endController = 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,
|
style: _headerStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
'Remarks',
|
||||||
|
style: _headerStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -563,6 +695,10 @@ class endosementState extends ConsumerState<Endorsement> {
|
|||||||
style: _headerStyle,
|
style: _headerStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text('Pending Days', style: _headerStyle),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text('Action', style: _headerStyle),
|
child: Text('Action', style: _headerStyle),
|
||||||
@ -840,7 +976,17 @@ class endosementState extends ConsumerState<Endorsement> {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child:
|
child:
|
||||||
Text(item['broker_name'] ?? '-', style: _dataBold)),
|
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(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: _buildBadge(
|
child: _buildBadge(
|
||||||
@ -858,21 +1004,35 @@ class endosementState extends ConsumerState<Endorsement> {
|
|||||||
type: 'verification',
|
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(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: item['is_active'] == "1"
|
child: item['is_active'] == "1"
|
||||||
? Row(
|
? Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
// --- EDIT ---
|
||||||
constraints: const BoxConstraints(),
|
_actionIconButton(
|
||||||
padding: EdgeInsets.zero,
|
context: context,
|
||||||
icon: Image.asset(
|
tooltip: 'Edit',
|
||||||
"assets/miscellaneous/Edit.png",
|
iconColor: const Color(0xFF319718),
|
||||||
height: 15,
|
onTap: () async {
|
||||||
width: 15),
|
|
||||||
onPressed: () async {
|
|
||||||
final result = await context.push(
|
final result = await context.push(
|
||||||
AppRoutes.endorsomentValidation,
|
AppRoutes.endorsomentValidation,
|
||||||
extra: {
|
extra: {
|
||||||
@ -883,33 +1043,71 @@ class endosementState extends ConsumerState<Endorsement> {
|
|||||||
"role": roleId,
|
"role": roleId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result == true) {
|
if (result == true) refresh();
|
||||||
refresh(); // 👈 reload list API
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
customIcon: Image.asset(
|
||||||
const SizedBox(width: 12),
|
"assets/miscellaneous/Edit.png",
|
||||||
InkWell(
|
height: 15,
|
||||||
onTap: () =>
|
width: 15,
|
||||||
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),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// --- 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// --- 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('-'),
|
: 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) {
|
// Widget _editButton(item) {
|
||||||
// return InkWell(
|
// return InkWell(
|
||||||
// onTap: () async {
|
// 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(
|
static final _dataBold = GoogleFonts.inter(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
|
|||||||
@ -70,9 +70,10 @@ class policylistState extends ConsumerState<policylist> {
|
|||||||
roleId = ref.read(userRoleProvider);
|
roleId = ref.read(userRoleProvider);
|
||||||
userId = ref.read(userIdProvider);
|
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) {
|
if (userId != null) {
|
||||||
getStaffList(managerId!, userId, roleId , selectedStatus : SelectedStatus ?? 'All');
|
getStaffList(managerId!, userId, roleId , selectedStatus : SelectedStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshSub = ref.listenManual<bool>(policyDataAcurancyRefreshProvider, (
|
refreshSub = ref.listenManual<bool>(policyDataAcurancyRefreshProvider, (
|
||||||
@ -83,7 +84,8 @@ class policylistState extends ConsumerState<policylist> {
|
|||||||
print('refresh triggered Quick Creation');
|
print('refresh triggered Quick Creation');
|
||||||
|
|
||||||
// callRefresh();
|
// 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;
|
ref.read(policyDataAcurancyRefreshProvider.notifier).state = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -92,12 +94,13 @@ class policylistState extends ConsumerState<policylist> {
|
|||||||
|
|
||||||
void refresh() {
|
void refresh() {
|
||||||
managerId = ref.read(managerIdProvider);
|
managerId = ref.read(managerIdProvider);
|
||||||
final roleId = ref.read(userRoleProvider);
|
roleId = ref.read(userRoleProvider);
|
||||||
final userId = ref.read(userIdProvider);
|
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) {
|
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;
|
SelectedStaffId = null;
|
||||||
controllers['startDate']!.clear();
|
controllers['startDate']!.clear();
|
||||||
controllers['endDate']!.clear();
|
controllers['endDate']!.clear();
|
||||||
SelectedStatus ='All';
|
SelectedStatus = (roleId == 'Accounts') ? 'Not Verified' : 'All'; // ✅
|
||||||
SelectedInsurer = '';
|
SelectedInsurer = '';
|
||||||
controllers['startDate']?.text = '';
|
controllers['startDate']?.text = '';
|
||||||
controllers['endDate']?.text = '';
|
controllers['endDate']?.text = '';
|
||||||
|
|||||||
@ -534,13 +534,17 @@ class _DateFilterRowState extends ConsumerState<DateFilterRow> {
|
|||||||
final List<Map<String, dynamic>> policyReportStatusOptions;
|
final List<Map<String, dynamic>> policyReportStatusOptions;
|
||||||
|
|
||||||
policyReportStatusOptions = [
|
policyReportStatusOptions = [
|
||||||
{'id': 'All', 'status': 'All'},
|
|
||||||
{'id': 'Verified', 'status': 'Verified'},
|
|
||||||
{'id': 'Not Verified', 'status': 'Not 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
|
Map<String, dynamic>? selectedPolicyReportStatusMap = policyReportStatusOptions
|
||||||
.where((element) => element['status'] == widget.selectedStatusVal)
|
.where((e) => e['status'] == effectiveStatus)
|
||||||
.cast<Map<String, dynamic>>()
|
.cast<Map<String, dynamic>>()
|
||||||
.toList()
|
.toList()
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
|
|||||||
22
pubspec.lock
22
pubspec.lock
@ -117,10 +117,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -780,26 +780,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.17.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1297,10 +1297,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.6"
|
version: "0.7.10"
|
||||||
toastification:
|
toastification:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1502,5 +1502,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.1 <4.0.0"
|
dart: ">=3.9.0-0 <4.0.0"
|
||||||
flutter: ">=3.32.0"
|
flutter: ">=3.32.0"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user