FIX_RATE
This commit is contained in:
parent
b3884f32c4
commit
650536745d
File diff suppressed because one or more lines are too long
@ -5,7 +5,7 @@ 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) */
|
||||||
);
|
);
|
||||||
|
|||||||
@ -918,6 +918,12 @@ class ApiService {
|
|||||||
Future<Map<String, dynamic>> loadPayoutGrid({
|
Future<Map<String, dynamic>> loadPayoutGrid({
|
||||||
String? role,
|
String? role,
|
||||||
String? fileId,
|
String? fileId,
|
||||||
|
String? insurer,
|
||||||
|
String? vehicleType,
|
||||||
|
String? segment,
|
||||||
|
String? rto,
|
||||||
|
String? planType,
|
||||||
|
String? search,
|
||||||
String? loggedId,
|
String? loggedId,
|
||||||
}) async {
|
}) async {
|
||||||
if (_token == null) {
|
if (_token == null) {
|
||||||
@ -926,21 +932,47 @@ class ApiService {
|
|||||||
|
|
||||||
final roleTrimmed = role?.trim();
|
final roleTrimmed = role?.trim();
|
||||||
final fileIdTrimmed = fileId?.trim();
|
final fileIdTrimmed = fileId?.trim();
|
||||||
|
final insurerTrimmed = insurer?.trim();
|
||||||
|
final vehicleTypeTrimmed = vehicleType?.trim();
|
||||||
|
final segmentTrimmed = segment?.trim();
|
||||||
|
final rtoTrimmed = rto?.trim();
|
||||||
|
final planTypeTrimmed = planType?.trim();
|
||||||
|
final searchTrimmed = search?.trim();
|
||||||
final loggedIdTrimmed = loggedId?.trim();
|
final loggedIdTrimmed = loggedId?.trim();
|
||||||
final endpoint = Uri.parse('${Env.apiUrl}grid');
|
final endpoint = Uri.parse('${Env.apiUrl}grid');
|
||||||
|
final roleLower = roleTrimmed?.toLowerCase();
|
||||||
|
|
||||||
final queryParameters = <String, String>{};
|
final queryParameters = <String, String>{};
|
||||||
if (roleTrimmed != null && roleTrimmed.isNotEmpty) {
|
if (roleTrimmed != null && roleTrimmed.isNotEmpty) {
|
||||||
queryParameters['role'] = roleTrimmed;
|
queryParameters['role'] = roleTrimmed;
|
||||||
}
|
}
|
||||||
if (fileIdTrimmed != null && fileIdTrimmed.isNotEmpty) {
|
if (roleLower != 'agent' &&
|
||||||
|
fileIdTrimmed != null &&
|
||||||
|
fileIdTrimmed.isNotEmpty) {
|
||||||
queryParameters['file_id'] = fileIdTrimmed;
|
queryParameters['file_id'] = fileIdTrimmed;
|
||||||
}
|
}
|
||||||
|
if (insurerTrimmed != null && insurerTrimmed.isNotEmpty) {
|
||||||
|
queryParameters['insurer'] = insurerTrimmed;
|
||||||
|
}
|
||||||
|
if (vehicleTypeTrimmed != null && vehicleTypeTrimmed.isNotEmpty) {
|
||||||
|
queryParameters['vehicle_type'] = vehicleTypeTrimmed;
|
||||||
|
}
|
||||||
|
if (segmentTrimmed != null && segmentTrimmed.isNotEmpty) {
|
||||||
|
queryParameters['segment'] = segmentTrimmed;
|
||||||
|
}
|
||||||
|
if (rtoTrimmed != null && rtoTrimmed.isNotEmpty) {
|
||||||
|
queryParameters['rto'] = rtoTrimmed;
|
||||||
|
}
|
||||||
|
if (planTypeTrimmed != null && planTypeTrimmed.isNotEmpty) {
|
||||||
|
queryParameters['plan_type'] = planTypeTrimmed;
|
||||||
|
}
|
||||||
|
if (searchTrimmed != null && searchTrimmed.isNotEmpty) {
|
||||||
|
queryParameters['search'] = searchTrimmed;
|
||||||
|
}
|
||||||
if (loggedIdTrimmed != null && loggedIdTrimmed.isNotEmpty) {
|
if (loggedIdTrimmed != null && loggedIdTrimmed.isNotEmpty) {
|
||||||
queryParameters['logged_id'] = loggedIdTrimmed;
|
queryParameters['logged_id'] = loggedIdTrimmed;
|
||||||
}
|
}
|
||||||
final url = endpoint.replace(queryParameters: queryParameters);
|
final url = endpoint.replace(queryParameters: queryParameters);
|
||||||
|
|
||||||
final headers = {
|
final headers = {
|
||||||
'Authorization': 'Bearer ${_token ?? ''}',
|
'Authorization': 'Bearer ${_token ?? ''}',
|
||||||
'app-signature': Env.App_Signature,
|
'app-signature': Env.App_Signature,
|
||||||
@ -1050,6 +1082,96 @@ class ApiService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> downloadAgentRetentionRateExcel() async {
|
||||||
|
if (_token == null) {
|
||||||
|
await _initializeToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
final url = Uri.parse('${Env.apiUrl}agent/exportRetentionRateExcel');
|
||||||
|
// final url = Uri.parse('http://localhost/nhance_partner_be/agent/exportRetentionRateExcel');
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer ${_token ?? ''}',
|
||||||
|
'app-signature': Env.App_Signature,
|
||||||
|
};
|
||||||
|
|
||||||
|
final response = await _makeGethttpRequest(url, headers);
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
throw Exception('Failed to download retention rate excel');
|
||||||
|
}
|
||||||
|
|
||||||
|
final contentType = response.headers['content-type'] ?? '';
|
||||||
|
if (contentType.contains('application/json')) {
|
||||||
|
throw Exception('No export data available');
|
||||||
|
}
|
||||||
|
|
||||||
|
final blob = html.Blob([response.bodyBytes]);
|
||||||
|
final blobUrl = html.Url.createObjectUrlFromBlob(blob);
|
||||||
|
final fileName = extractFileName(
|
||||||
|
response.headers['content-disposition'],
|
||||||
|
'RentationRate.xlsx',
|
||||||
|
);
|
||||||
|
html.AnchorElement(href: blobUrl)
|
||||||
|
..setAttribute('download', fileName)
|
||||||
|
..click();
|
||||||
|
html.Url.revokeObjectUrl(blobUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> importAgentRetentionRateExcel({
|
||||||
|
required PlatformFile file,
|
||||||
|
}) async {
|
||||||
|
if (_token == null) {
|
||||||
|
await _initializeToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
final url = Uri.parse('${Env.apiUrl}agent/importRetentionRateExcel');
|
||||||
|
// final url = Uri.parse('http://localhost/nhance_partner_be/agent/importRetentionRateExcel');
|
||||||
|
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 selected file');
|
||||||
|
}
|
||||||
|
|
||||||
|
request.files.add(
|
||||||
|
http.MultipartFile.fromBytes(
|
||||||
|
'retention_excel',
|
||||||
|
file.bytes!,
|
||||||
|
filename: file.name,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final streamedResponse = await request.send();
|
||||||
|
final responseBody = await streamedResponse.stream.bytesToString();
|
||||||
|
|
||||||
|
if (streamedResponse.statusCode == 401 ||
|
||||||
|
streamedResponse.statusCode == 403) {
|
||||||
|
await clearLocalStorageAndRedirect();
|
||||||
|
return {'status': 'error', 'message': 'Session expired'};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseBody.trim().isEmpty) {
|
||||||
|
return {
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Empty response from server',
|
||||||
|
'code': streamedResponse.statusCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final decoded = jsonDecode(responseBody);
|
||||||
|
if (decoded is Map<String, dynamic>) {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'status': 'error', 'message': 'Unexpected response format'};
|
||||||
|
} catch (e) {
|
||||||
|
return {'status': 'error', 'message': e.toString()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> findSingleAgentData(id) async {
|
Future<Map<String, dynamic>> findSingleAgentData(id) async {
|
||||||
// print(_token);
|
// print(_token);
|
||||||
if (_token == null) {
|
if (_token == null) {
|
||||||
|
|||||||
@ -24,13 +24,19 @@ Future<void> _restoreManagerId(WidgetRef ref) async {
|
|||||||
'attendanceIndvStaffName',
|
'attendanceIndvStaffName',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (savedManagerId != null && savedUserId != null) {
|
// Partners (agents) often have no manager_id in JWT, so never gate role/userId
|
||||||
ref.read(managerIdProvider.notifier).state = savedManagerId;
|
// restore on managerId — without role, grid `?role=Agent&logged_id=` is omitted and
|
||||||
|
// the payout grid endpoint misbehaves for agent logins after app restart.
|
||||||
|
if (savedUserId != null) {
|
||||||
ref.read(userIdProvider.notifier).state = savedUserId;
|
ref.read(userIdProvider.notifier).state = savedUserId;
|
||||||
ref.read(userRoleProvider.notifier).state = savedUserRole;
|
|
||||||
|
|
||||||
print("Manager ID restored: $savedManagerId");
|
|
||||||
print("User ID restored: $savedUserId");
|
print("User ID restored: $savedUserId");
|
||||||
|
}
|
||||||
|
if (savedManagerId != null) {
|
||||||
|
ref.read(managerIdProvider.notifier).state = savedManagerId;
|
||||||
|
print("Manager ID restored: $savedManagerId");
|
||||||
|
}
|
||||||
|
if (savedUserRole != null && savedUserRole.trim().isNotEmpty) {
|
||||||
|
ref.read(userRoleProvider.notifier).state = savedUserRole;
|
||||||
print("Role restored: $savedUserRole");
|
print("Role restored: $savedUserRole");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -554,9 +554,9 @@ class _gridViewScreenState extends ConsumerState<gridViewScreen> {
|
|||||||
if (_showAddButton) {
|
if (_showAddButton) {
|
||||||
return rows.map((row) {
|
return rows.map((row) {
|
||||||
final next = Map<String, dynamic>.from(row);
|
final next = Map<String, dynamic>.from(row);
|
||||||
next['partner_comp'] = next['comp'];
|
next['partner_comp'] = next['oa'] ?? next['comp'];
|
||||||
next['partner_tp'] = next['tp'];
|
next['partner_tp'] = next['ob'] ?? next['tp'];
|
||||||
next['partner_od'] = next['od'];
|
next['partner_od'] = next['oc'] ?? next['od'];
|
||||||
return next;
|
return next;
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
@ -1611,9 +1611,13 @@ class _gridViewScreenState extends ConsumerState<gridViewScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final loggedIdForGrid =
|
||||||
|
(effectiveRole ?? '').toLowerCase() == 'agent' ? _loggedId : null;
|
||||||
|
|
||||||
final res = await _apiService.loadPayoutGrid(
|
final res = await _apiService.loadPayoutGrid(
|
||||||
role: effectiveRole,
|
role: effectiveRole,
|
||||||
fileId: effectiveFileId,
|
fileId: effectiveFileId,
|
||||||
|
loggedId: loggedIdForGrid,
|
||||||
);
|
);
|
||||||
if ((res['status'] ?? '').toString().toLowerCase() != 'success') {
|
if ((res['status'] ?? '').toString().toLowerCase() != 'success') {
|
||||||
ToastHelper.showErrorToast(
|
ToastHelper.showErrorToast(
|
||||||
@ -2443,9 +2447,13 @@ class _gridViewScreenState extends ConsumerState<gridViewScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final loggedIdForGrid =
|
||||||
|
(effectiveRole ?? '').toLowerCase() == 'agent' ? _loggedId : null;
|
||||||
|
|
||||||
final res = await _apiService.loadPayoutGrid(
|
final res = await _apiService.loadPayoutGrid(
|
||||||
role: effectiveRole,
|
role: effectiveRole,
|
||||||
fileId: effectiveFileId,
|
fileId: effectiveFileId,
|
||||||
|
loggedId: loggedIdForGrid,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ((res['status'] ?? '').toString().toLowerCase() != 'success') {
|
if ((res['status'] ?? '').toString().toLowerCase() != 'success') {
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
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';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.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';
|
||||||
@ -34,6 +36,7 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
List<Map<String, dynamic>> originalData = [];
|
List<Map<String, dynamic>> originalData = [];
|
||||||
List<Map<String, dynamic>> filteredData = [];
|
List<Map<String, dynamic>> filteredData = [];
|
||||||
bool isLoading = false;
|
bool isLoading = false;
|
||||||
|
bool isRetentionImportLoading = false;
|
||||||
dynamic sortedData;
|
dynamic sortedData;
|
||||||
dynamic managerID;
|
dynamic managerID;
|
||||||
@override
|
@override
|
||||||
@ -110,9 +113,6 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
(item['sales_executive_name'] ?? '-').toLowerCase().contains(
|
(item['sales_executive_name'] ?? '-').toLowerCase().contains(
|
||||||
query.toLowerCase(),
|
query.toLowerCase(),
|
||||||
) ||
|
) ||
|
||||||
(item['retention_rate'] ?? '-').toLowerCase().contains(
|
|
||||||
query.toLowerCase(),
|
|
||||||
) ||
|
|
||||||
(isActiveStatus.contains(query.toLowerCase()));
|
(isActiveStatus.contains(query.toLowerCase()));
|
||||||
}).toList();
|
}).toList();
|
||||||
});
|
});
|
||||||
@ -155,6 +155,232 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _exportRetentionRateExcel() async {
|
||||||
|
final proceed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Export Rentation Rate'),
|
||||||
|
content: const Text(
|
||||||
|
'The downloaded Excel will include input validation.\n\n'
|
||||||
|
'Allowed values: only numbers from 0 to 100.\n'
|
||||||
|
'Not allowed: negative values, values above 100, text/special characters.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, false),
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
|
child: const Text('Download'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (proceed != true) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiService.downloadAgentRetentionRateExcel();
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showSuccessToast(
|
||||||
|
context,
|
||||||
|
'Rentation rate Excel downloaded',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showErrorToast(
|
||||||
|
context,
|
||||||
|
'Failed to export rentation rate Excel',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _importRetentionRateExcel() async {
|
||||||
|
final picked = await FilePicker.platform.pickFiles(
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: const ['xls', 'xlsx'],
|
||||||
|
withData: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picked == null || picked.files.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final file = picked.files.first;
|
||||||
|
if (file.bytes == null) {
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showErrorToast(context, 'Could not read selected file');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isRetentionImportLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await apiService.importAgentRetentionRateExcel(file: file);
|
||||||
|
final status = (response['status'] ?? '').toString().toLowerCase();
|
||||||
|
if (status == 'success') {
|
||||||
|
final data = (response['data'] is Map<String, dynamic>)
|
||||||
|
? response['data'] as Map<String, dynamic>
|
||||||
|
: <String, dynamic>{};
|
||||||
|
final report = (response['report'] is Map<String, dynamic>)
|
||||||
|
? response['report'] as Map<String, dynamic>
|
||||||
|
: <String, dynamic>{};
|
||||||
|
|
||||||
|
final inserted = (data['inserted_cells'] ?? 0).toString();
|
||||||
|
final updated = (data['updated_cells'] ?? 0).toString();
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showSuccessToast(
|
||||||
|
context,
|
||||||
|
'Import done',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await _showRetentionImportReportDialog(data, report);
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showErrorToast(context, 'Import failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ToastHelper.showErrorToast(context, 'Failed to import rentation rate');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
isRetentionImportLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showRetentionImportReportDialog(
|
||||||
|
Map<String, dynamic> data,
|
||||||
|
Map<String, dynamic> report,
|
||||||
|
) async {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final inserted = data['inserted_cells'] ?? 0;
|
||||||
|
final updated = data['updated_cells'] ?? 0;
|
||||||
|
final skippedZero = data['skipped_zero'] ?? 0;
|
||||||
|
final skippedSame = data['skipped_same'] ?? 0;
|
||||||
|
final skippedEmptyRow = data['skipped_empty_row'] ?? 0;
|
||||||
|
final skippedEmptyCell = data['skipped_empty_cell'] ?? 0;
|
||||||
|
final skippedUnknownAgent = data['skipped_unknown_agent'] ?? 0;
|
||||||
|
final invalidRange = data['invalid_range'] ?? 0;
|
||||||
|
final invalidNumber = data['invalid_number'] ?? 0;
|
||||||
|
|
||||||
|
final unknownAgents = (report['unknown_agent_codes'] as List?)
|
||||||
|
?.map((e) => e.toString())
|
||||||
|
.where((e) => e.trim().isNotEmpty)
|
||||||
|
.toList() ??
|
||||||
|
<String>[];
|
||||||
|
final unknownVehicleTypes = (report['unknown_vehicle_types'] as List?)
|
||||||
|
?.map((e) => e.toString())
|
||||||
|
.where((e) => e.trim().isNotEmpty)
|
||||||
|
.toList() ??
|
||||||
|
<String>[];
|
||||||
|
final invalidCells = (report['invalid_cells'] as List?)
|
||||||
|
?.map((e) => e.toString())
|
||||||
|
.where((e) => e.trim().isNotEmpty)
|
||||||
|
.toList() ??
|
||||||
|
<String>[];
|
||||||
|
final agentWiseSkippedRaw = (report['agent_wise_skipped_counts'] is Map)
|
||||||
|
? Map<String, dynamic>.from(report['agent_wise_skipped_counts'] as Map)
|
||||||
|
: <String, dynamic>{};
|
||||||
|
|
||||||
|
final lines = <String>[
|
||||||
|
'Inserted: $inserted',
|
||||||
|
'Updated: $updated',
|
||||||
|
// 'Skipped (value = 0): $skippedZero',
|
||||||
|
// 'Skipped (Excel value equals DB value): $skippedSame',
|
||||||
|
// 'Skipped (empty first column / agent code): $skippedEmptyRow',
|
||||||
|
// 'Skipped (empty cell): $skippedEmptyCell',
|
||||||
|
// 'Skipped (unknown agent code): $skippedUnknownAgent',
|
||||||
|
'Skipped (out of range 0-100): $invalidRange',
|
||||||
|
'Skipped (non-numeric value): $invalidNumber',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (unknownAgents.isNotEmpty) {
|
||||||
|
lines.add('');
|
||||||
|
lines.add('Unknown Agent Codes:');
|
||||||
|
lines.addAll(unknownAgents.take(10).map((e) => '- $e'));
|
||||||
|
if (unknownAgents.length > 10) {
|
||||||
|
lines.add('- ...and ${unknownAgents.length - 10} more');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unknownVehicleTypes.isNotEmpty) {
|
||||||
|
lines.add('');
|
||||||
|
lines.add('Unknown Vehicle Types (header):');
|
||||||
|
lines.addAll(unknownVehicleTypes.take(10).map((e) => '- $e'));
|
||||||
|
if (unknownVehicleTypes.length > 10) {
|
||||||
|
lines.add('- ...and ${unknownVehicleTypes.length - 10} more');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invalidCells.isNotEmpty) {
|
||||||
|
lines.add('');
|
||||||
|
lines.add('Invalid Cell Samples:');
|
||||||
|
lines.addAll(invalidCells.take(10).map((e) => '- $e'));
|
||||||
|
if (invalidCells.length > 10) {
|
||||||
|
lines.add('- ...and ${invalidCells.length - 10} more');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agentWiseSkippedRaw.isNotEmpty) {
|
||||||
|
final sorted = agentWiseSkippedRaw.entries.toList()
|
||||||
|
..sort((a, b) {
|
||||||
|
final av = int.tryParse(a.value.toString()) ?? 0;
|
||||||
|
final bv = int.tryParse(b.value.toString()) ?? 0;
|
||||||
|
return bv.compareTo(av);
|
||||||
|
});
|
||||||
|
final nonZero = sorted
|
||||||
|
.where((e) => (int.tryParse(e.value.toString()) ?? 0) > 0)
|
||||||
|
.toList();
|
||||||
|
if (nonZero.isNotEmpty) {
|
||||||
|
lines.add('');
|
||||||
|
lines.add('Skipped Count (Agent Wise):');
|
||||||
|
lines.addAll(
|
||||||
|
nonZero
|
||||||
|
.take(20)
|
||||||
|
.map((e) => '- ${e.key}: ${int.tryParse(e.value.toString()) ?? 0}'),
|
||||||
|
);
|
||||||
|
if (nonZero.length > 20) {
|
||||||
|
lines.add('- ...and ${nonZero.length - 20} more');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (ctx) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Rentation Rate Import Report'),
|
||||||
|
content: SizedBox(
|
||||||
|
width: 520,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: SelectableText(lines.join('\n')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(ctx),
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> _buildPopupMenuActions(BuildContext context, dynamic data) {
|
List<Widget> _buildPopupMenuActions(BuildContext context, dynamic data) {
|
||||||
return [
|
return [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
@ -266,6 +492,81 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
txtwidth: MediaQuery.of(context).size.width * 0.15,
|
txtwidth: MediaQuery.of(context).size.width * 0.15,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
SizedBox(width: 10),
|
||||||
|
InkWell(
|
||||||
|
onTap: _exportRetentionRateExcel,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF2E7D6E),
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.file_download_outlined,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Export Rentation Rate',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
InkWell(
|
||||||
|
onTap: isRetentionImportLoading
|
||||||
|
? null
|
||||||
|
: _importRetentionRateExcel,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF2E7D6E),
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
isRetentionImportLoading
|
||||||
|
? SizedBox(
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.file_upload_outlined,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Import Rentation Rate',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(width: 10),
|
SizedBox(width: 10),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -317,7 +618,6 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
'Email',
|
'Email',
|
||||||
'Sales Executive Name',
|
'Sales Executive Name',
|
||||||
'Phone Number',
|
'Phone Number',
|
||||||
'Retention Rate',
|
|
||||||
'Address',
|
'Address',
|
||||||
'Status',
|
'Status',
|
||||||
],
|
],
|
||||||
@ -328,7 +628,6 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
"email",
|
"email",
|
||||||
"sales_executive_name",
|
"sales_executive_name",
|
||||||
"mobile",
|
"mobile",
|
||||||
"retention_rate",
|
|
||||||
"address",
|
"address",
|
||||||
"is_active",
|
"is_active",
|
||||||
],
|
],
|
||||||
@ -414,13 +713,6 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text('Sales Executive Name', style: _headerStyle),
|
child: Text('Sales Executive Name', style: _headerStyle),
|
||||||
),
|
),
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Text(
|
|
||||||
'Retention Rate ',
|
|
||||||
style: _headerStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text('Address', style: _headerStyle),
|
child: Text('Address', style: _headerStyle),
|
||||||
@ -542,10 +834,6 @@ class AgentListState extends ConsumerState<AgentList> {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(item['sales_executive_name'] ?? '-', style: _dataBold),
|
child: Text(item['sales_executive_name'] ?? '-', style: _dataBold),
|
||||||
),
|
),
|
||||||
Expanded(
|
|
||||||
flex: 1,
|
|
||||||
child: Text(item['retention_rate'] ?? '-', style: _dataBold),
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
-->
|
-->
|
||||||
<!-- <base href="$FLUTTER_BASE_HREF"> -->
|
<!-- <base href="$FLUTTER_BASE_HREF"> -->
|
||||||
<!-- <base href="/partner/"> --> <!-- Live build: also check env.dart (line 8) -->
|
<!-- <base href="/partner/"> --> <!-- Live build: also check env.dart (line 8) -->
|
||||||
<base href="/nhance/partner/app/">
|
<base href="/nhance/partner/app/">
|
||||||
<!-- <base href="{Env.baseHref}">-->
|
<!-- <base href="{Env.baseHref}">-->
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user