186 lines
6.4 KiB
Dart
186 lines
6.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:lucide_icons/lucide_icons.dart';
|
|
import 'package:intl/intl.dart';
|
|
import '../../../transactions/providers/providers.dart';
|
|
import '../../../transactions/data/models.dart';
|
|
import '../../../../core/theme/nature_colors.dart';
|
|
|
|
class UpcomingDuesWidget extends ConsumerWidget {
|
|
const UpcomingDuesWidget({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final walletsState = ref.watch(walletProvider);
|
|
|
|
if (!walletsState.hasValue || walletsState.value == null) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
final wallets = walletsState.value!;
|
|
|
|
// Filter for payables that have a due date/amount
|
|
final payables = wallets.where((w) => w.nature == 'PAYABLES').toList();
|
|
|
|
if (payables.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
final now = DateTime.now();
|
|
|
|
// Calculate dues
|
|
final List<Map<String, dynamic>> dues = [];
|
|
|
|
for (var w in payables) {
|
|
double dueAmount = 0;
|
|
DateTime? dueDate;
|
|
String dueLabel = 'Upcoming Due';
|
|
|
|
if (w.subNature == 'CREDIT_CARD' || w.subNature == 'OD_LIMIT') {
|
|
// Balance is negative if we owe money
|
|
if (w.balance < 0) {
|
|
dueAmount = w.balance.abs();
|
|
}
|
|
} else if (w.subNature == 'LOAN_EMI' || w.subNature == 'POLICY_PREMIUM') {
|
|
dueAmount = w.fixedAmount ?? 0;
|
|
}
|
|
|
|
if (dueAmount > 0 && w.cycleDate != null) {
|
|
int year = now.year;
|
|
int month = now.month;
|
|
|
|
// Find next due date based on cycle
|
|
if (w.paymentCycle == 'MONTHLY' || w.paymentCycle == null) {
|
|
if (now.day > w.cycleDate!) {
|
|
// Due date has passed for this month, next is next month
|
|
month++;
|
|
if (month > 12) {
|
|
month = 1;
|
|
year++;
|
|
}
|
|
}
|
|
} else if (w.paymentCycle == 'YEARLY') {
|
|
// Assume cycleDate is day of current month, this is simplistic
|
|
if (now.day > w.cycleDate!) {
|
|
year++;
|
|
}
|
|
}
|
|
|
|
// Handle end of month issues (e.g. Feb 30th)
|
|
int maxDays = DateTime(year, month + 1, 0).day;
|
|
int day = w.cycleDate! > maxDays ? maxDays : w.cycleDate!;
|
|
|
|
dueDate = DateTime(year, month, day);
|
|
|
|
int daysLeft = dueDate.difference(DateTime(now.year, now.month, now.day)).inDays;
|
|
|
|
if (daysLeft == 0) {
|
|
dueLabel = 'Due Today';
|
|
} else if (daysLeft == 1) {
|
|
dueLabel = 'Due Tomorrow';
|
|
} else {
|
|
dueLabel = 'Due in $daysLeft days';
|
|
}
|
|
|
|
dues.add({
|
|
'wallet': w,
|
|
'amount': dueAmount,
|
|
'dueDate': dueDate,
|
|
'label': dueLabel,
|
|
'daysLeft': daysLeft,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (dues.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
// Sort by nearest due date
|
|
dues.sort((a, b) => (a['daysLeft'] as int).compareTo(b['daysLeft'] as int));
|
|
|
|
// Only show top 3 dues
|
|
final displayDues = dues.take(3).toList();
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const Icon(LucideIcons.calendarClock, color: Color(0xFF6C63FF), size: 20),
|
|
const SizedBox(width: 8),
|
|
Text('Upcoming Dues', style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold)),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
...displayDues.map((due) {
|
|
final w = due['wallet'] as Wallet;
|
|
final amount = due['amount'] as double;
|
|
final dueDate = due['dueDate'] as DateTime;
|
|
final label = due['label'] as String;
|
|
final daysLeft = due['daysLeft'] as int;
|
|
|
|
final isUrgent = daysLeft <= 3;
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: isUrgent ? Colors.red.shade50 : Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: isUrgent ? Colors.red.shade200 : Colors.grey.shade200),
|
|
boxShadow: [
|
|
if (!isUrgent) BoxShadow(color: Colors.black.withOpacity(0.02), blurRadius: 8, offset: const Offset(0, 2))
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: (isUrgent ? Colors.red : NatureColors.getColor('PAYABLES')).withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
w.subNature == 'CREDIT_CARD' ? LucideIcons.creditCard :
|
|
(w.subNature == 'LOAN_EMI' ? LucideIcons.home : LucideIcons.fileText),
|
|
color: isUrgent ? Colors.red : NatureColors.getColor('PAYABLES'),
|
|
size: 20,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(w.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15)),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'$label • ${DateFormat('MMM dd').format(dueDate)}',
|
|
style: TextStyle(
|
|
color: isUrgent ? Colors.red.shade700 : Colors.grey.shade600,
|
|
fontSize: 12,
|
|
fontWeight: isUrgent ? FontWeight.bold : FontWeight.normal
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Text(
|
|
'Rs. ${amount.toStringAsFixed(0)}',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
color: isUrgent ? Colors.red.shade700 : Colors.black87
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
const SizedBox(height: 12),
|
|
],
|
|
);
|
|
}
|
|
}
|