752 lines
23 KiB
Dart
752 lines
23 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import '../../core/services/api_service.dart';
|
|
|
|
/// Transaction Sync Dialog
|
|
/// Shows PhonePe transactions and suggests payment matches
|
|
class TransactionSyncDialog extends StatefulWidget {
|
|
const TransactionSyncDialog({super.key});
|
|
|
|
@override
|
|
State<TransactionSyncDialog> createState() => _TransactionSyncDialogState();
|
|
}
|
|
|
|
class _TransactionSyncDialogState extends State<TransactionSyncDialog> {
|
|
final _apiService = ApiService();
|
|
|
|
bool _isSyncing = false;
|
|
bool _isLoadingReview = false;
|
|
Map<String, dynamic>? _syncResults;
|
|
List<dynamic> _reviewQueue = [];
|
|
int _daysBack = 7;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadReviewQueue();
|
|
}
|
|
|
|
Future<void> _loadReviewQueue() async {
|
|
setState(() {
|
|
_isLoadingReview = true;
|
|
});
|
|
|
|
try {
|
|
final response = await _apiService.get('/transaction-sync/review-queue');
|
|
|
|
if (response['success'] == true) {
|
|
setState(() {
|
|
_reviewQueue = response['data']['review_queue'] ?? [];
|
|
_isLoadingReview = false;
|
|
});
|
|
} else {
|
|
setState(() {
|
|
_isLoadingReview = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
_isLoadingReview = false;
|
|
});
|
|
Get.snackbar(
|
|
'Error',
|
|
'Failed to load review queue: $e',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _syncTransactions() async {
|
|
setState(() {
|
|
_isSyncing = true;
|
|
_syncResults = null;
|
|
});
|
|
|
|
try {
|
|
final response = await _apiService.post(
|
|
'/transaction-sync/sync',
|
|
{'days_back': _daysBack},
|
|
);
|
|
|
|
if (response['success'] == true) {
|
|
setState(() {
|
|
_syncResults = response['data'];
|
|
_isSyncing = false;
|
|
});
|
|
|
|
Get.snackbar(
|
|
'Sync Complete!',
|
|
'${response['data']['autoRecorded']} payments auto-recorded, '
|
|
'${response['data']['needsReview']} need review',
|
|
backgroundColor: Colors.green.shade100,
|
|
colorText: Colors.green.shade800,
|
|
icon: Icon(Icons.check_circle, color: Colors.green.shade600),
|
|
duration: Duration(seconds: 4),
|
|
);
|
|
|
|
// Reload review queue
|
|
await _loadReviewQueue();
|
|
} else {
|
|
setState(() {
|
|
_isSyncing = false;
|
|
});
|
|
Get.snackbar(
|
|
'Sync Failed',
|
|
response['message'] ?? 'Failed to sync transactions',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
setState(() {
|
|
_isSyncing = false;
|
|
});
|
|
Get.snackbar(
|
|
'Error',
|
|
'Failed to sync: $e',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _approveMatch(dynamic item) async {
|
|
final transaction = item['transaction'];
|
|
final match = item['suggestedMatch'];
|
|
|
|
try {
|
|
final txnDate = DateTime.parse(transaction['transactionDate']);
|
|
|
|
final response = await _apiService.post(
|
|
'/transaction-sync/approve',
|
|
{
|
|
'transaction_id': transaction['id'],
|
|
'group_id': match['group']['id'],
|
|
'user_id': match['member']['User']['id'],
|
|
'month': txnDate.month,
|
|
'year': txnDate.year,
|
|
'amount': transaction['amount'] / 100,
|
|
},
|
|
);
|
|
|
|
if (response['success'] == true) {
|
|
Get.snackbar(
|
|
'Approved!',
|
|
'Payment recorded successfully',
|
|
backgroundColor: Colors.green.shade100,
|
|
colorText: Colors.green.shade800,
|
|
icon: Icon(Icons.check_circle, color: Colors.green.shade600),
|
|
);
|
|
|
|
// Remove from review queue
|
|
setState(() {
|
|
_reviewQueue.remove(item);
|
|
});
|
|
} else {
|
|
Get.snackbar(
|
|
'Failed',
|
|
response['message'] ?? 'Could not approve match',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
'Error',
|
|
'Failed to approve: $e',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _rejectMatch(dynamic item) async {
|
|
final transaction = item['transaction'];
|
|
|
|
try {
|
|
await _apiService.post(
|
|
'/transaction-sync/reject',
|
|
{
|
|
'transaction_id': transaction['id'],
|
|
'reason': 'Manager rejected',
|
|
},
|
|
);
|
|
|
|
// Remove from review queue
|
|
setState(() {
|
|
_reviewQueue.remove(item);
|
|
});
|
|
|
|
Get.snackbar(
|
|
'Rejected',
|
|
'Match rejected',
|
|
backgroundColor: Colors.grey.shade100,
|
|
colorText: Colors.grey.shade800,
|
|
);
|
|
} catch (e) {
|
|
Get.snackbar(
|
|
'Error',
|
|
'Failed to reject: $e',
|
|
backgroundColor: Colors.red.shade100,
|
|
colorText: Colors.red.shade800,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
),
|
|
child: Container(
|
|
width: double.infinity,
|
|
constraints: BoxConstraints(
|
|
maxWidth: 600.w,
|
|
maxHeight: 0.85.sh,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: EdgeInsets.all(20.w),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Colors.blue.shade600,
|
|
Colors.blue.shade700,
|
|
],
|
|
),
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(16.r),
|
|
topRight: Radius.circular(16.r),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
child: Icon(
|
|
Icons.sync,
|
|
color: Colors.white,
|
|
size: 24.w,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Auto-Sync Payments',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
Text(
|
|
'Import PhonePe transactions',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon: Icon(Icons.close, color: Colors.white, size: 24.w),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Content
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: EdgeInsets.all(20.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Sync Controls
|
|
_buildSyncControls(),
|
|
|
|
if (_syncResults != null) ...[
|
|
SizedBox(height: 20.h),
|
|
_buildSyncResults(),
|
|
],
|
|
|
|
if (_reviewQueue.isNotEmpty) ...[
|
|
SizedBox(height: 24.h),
|
|
_buildReviewQueueSection(),
|
|
],
|
|
|
|
if (_reviewQueue.isEmpty && !_isLoadingReview && _syncResults == null) ...[
|
|
SizedBox(height: 20.h),
|
|
_buildEmptyState(),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSyncControls() {
|
|
return Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.blue.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.cloud_download, color: Colors.blue.shade700, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'Pull Transactions from PhonePe',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.blue.shade900,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Days selection
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'Last',
|
|
style: TextStyle(fontSize: 14.sp, color: Colors.grey.shade700),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
DropdownButton<int>(
|
|
value: _daysBack,
|
|
items: [7, 14, 30, 60, 90].map((days) {
|
|
return DropdownMenuItem<int>(
|
|
value: days,
|
|
child: Text('$days days', style: TextStyle(fontSize: 14.sp)),
|
|
);
|
|
}).toList(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_daysBack = value!;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 16.h),
|
|
|
|
// Sync button
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton.icon(
|
|
onPressed: _isSyncing ? null : _syncTransactions,
|
|
icon: _isSyncing
|
|
? SizedBox(
|
|
width: 20.w,
|
|
height: 20.h,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
|
),
|
|
)
|
|
: Icon(Icons.sync, size: 20.w),
|
|
label: Text(
|
|
_isSyncing ? 'Syncing...' : 'Sync Transactions',
|
|
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 14.h),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
'• Auto-matches by phone number, name, and amount\n'
|
|
'• High-confidence matches auto-recorded\n'
|
|
'• Low-confidence matches need your review',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.blue.shade700,
|
|
height: 1.5,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSyncResults() {
|
|
return Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.green.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.check_circle, color: Colors.green.shade700, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'Sync Results',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade900,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
_buildResultRow('Total Transactions', _syncResults!['total'].toString()),
|
|
_buildResultRow('Auto-Recorded', _syncResults!['autoRecorded'].toString(),
|
|
color: Colors.green.shade700),
|
|
_buildResultRow('Need Review', _syncResults!['needsReview'].toString(),
|
|
color: Colors.orange.shade700),
|
|
_buildResultRow('Could Not Match', _syncResults!['failed'].toString(),
|
|
color: Colors.red.shade700),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildResultRow(String label, String value, {Color? color}) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 4.h),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(fontSize: 14.sp, color: Colors.grey.shade700),
|
|
),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w700,
|
|
color: color ?? Colors.grey.shade900,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildReviewQueueSection() {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Review Queue',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
|
decoration: BoxDecoration(
|
|
color: Colors.orange.shade100,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Text(
|
|
'${_reviewQueue.length}',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.orange.shade800,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Text(
|
|
'These transactions need your confirmation:',
|
|
style: TextStyle(fontSize: 14.sp, color: Colors.grey.shade600),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
..._reviewQueue.map((item) => _buildReviewCard(item)),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildReviewCard(dynamic item) {
|
|
final transaction = item['transaction'];
|
|
final match = item['suggestedMatch'];
|
|
|
|
final amount = (transaction['amount'] / 100).toStringAsFixed(2);
|
|
final payerName = transaction['payerName'] ?? 'Unknown';
|
|
final payerPhone = transaction['payerPhone'] ?? '';
|
|
final date = DateTime.parse(transaction['transactionDate']);
|
|
final confidence = match['confidence'];
|
|
final matchedBy = match['matchedBy'];
|
|
|
|
final groupName = match['group']['name'];
|
|
final memberName = match['member']?['User']?['full_name'] ?? 'Unknown';
|
|
final memberPhone = match['member']?['User']?['mobile_number'] ?? '';
|
|
|
|
return Container(
|
|
margin: EdgeInsets.only(bottom: 12.h),
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.orange.shade200),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 4.r,
|
|
offset: Offset(0, 2.h),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Transaction Details
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.account_balance_wallet,
|
|
size: 18.w, color: Colors.blue.shade600),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
'₹$amount',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w800,
|
|
color: Colors.blue.shade700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h),
|
|
decoration: BoxDecoration(
|
|
color: _getConfidenceColor(confidence).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: _getConfidenceColor(confidence)),
|
|
),
|
|
child: Text(
|
|
confidence.toUpperCase().replaceAll('_', ' '),
|
|
style: TextStyle(
|
|
fontSize: 11.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: _getConfidenceColor(confidence),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.h),
|
|
|
|
// Payer info
|
|
Row(
|
|
children: [
|
|
Icon(Icons.person, size: 14.w, color: Colors.grey.shade600),
|
|
SizedBox(width: 6.w),
|
|
Expanded(
|
|
child: Text(
|
|
'$payerName ${payerPhone.isNotEmpty ? "• $payerPhone" : ""}',
|
|
style: TextStyle(fontSize: 13.sp, color: Colors.grey.shade700),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4.h),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.calendar_today, size: 14.w, color: Colors.grey.shade600),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
'${date.day}/${date.month}/${date.year}',
|
|
style: TextStyle(fontSize: 13.sp, color: Colors.grey.shade700),
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 12.h),
|
|
Divider(height: 1, color: Colors.grey.shade300),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Suggested Match
|
|
Row(
|
|
children: [
|
|
Icon(Icons.auto_fix_high, size: 16.w, color: Colors.orange.shade600),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
'Suggested Match:',
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
groupName,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.grey.shade900,
|
|
),
|
|
),
|
|
SizedBox(height: 6.h),
|
|
Text(
|
|
'$memberName ${memberPhone.isNotEmpty ? "• $memberPhone" : ""}',
|
|
style: TextStyle(fontSize: 13.sp, color: Colors.grey.shade700),
|
|
),
|
|
SizedBox(height: 6.h),
|
|
Text(
|
|
'Matched by: $matchedBy',
|
|
style: TextStyle(
|
|
fontSize: 11.sp,
|
|
color: Colors.grey.shade600,
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
SizedBox(height: 16.h),
|
|
|
|
// Action Buttons
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton.icon(
|
|
onPressed: () => _rejectMatch(item),
|
|
icon: Icon(Icons.close, size: 18.w),
|
|
label: Text('Reject', style: TextStyle(fontSize: 14.sp)),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: Colors.red.shade600,
|
|
side: BorderSide(color: Colors.red.shade600),
|
|
padding: EdgeInsets.symmetric(vertical: 10.h),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
flex: 2,
|
|
child: ElevatedButton.icon(
|
|
onPressed: () => _approveMatch(item),
|
|
icon: Icon(Icons.check, size: 18.w),
|
|
label: Text('Approve & Record', style: TextStyle(fontSize: 14.sp)),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green.shade600,
|
|
foregroundColor: Colors.white,
|
|
padding: EdgeInsets.symmetric(vertical: 10.h),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyState() {
|
|
return Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(32.w),
|
|
child: Column(
|
|
children: [
|
|
Icon(
|
|
Icons.check_circle_outline,
|
|
size: 64.w,
|
|
color: Colors.green.shade400,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
'All Caught Up!',
|
|
style: TextStyle(
|
|
fontSize: 20.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
'No transactions need review.\nTap "Sync Transactions" to check for new payments.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Color _getConfidenceColor(String confidence) {
|
|
switch (confidence) {
|
|
case 'very_high':
|
|
return Colors.green.shade600;
|
|
case 'high':
|
|
return Colors.lightGreen.shade600;
|
|
case 'medium':
|
|
return Colors.orange.shade600;
|
|
case 'low':
|
|
return Colors.red.shade600;
|
|
default:
|
|
return Colors.grey.shade600;
|
|
}
|
|
}
|
|
}
|
|
|