add payments screen

This commit is contained in:
Phuoc Nguyen
2025-10-27 10:45:03 +07:00
parent 830ef7e2a2
commit 90a02e1000
8 changed files with 1508 additions and 5 deletions

View File

@@ -0,0 +1,168 @@
/// Providers: Invoices
///
/// Riverpod providers for managing invoices state.
library;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:worker/core/database/models/enums.dart';
import 'package:worker/features/orders/data/datasources/invoices_local_datasource.dart';
import 'package:worker/features/orders/data/models/invoice_model.dart';
part 'invoices_provider.g.dart';
/// Invoices Local Data Source Provider
@riverpod
InvoicesLocalDataSource invoicesLocalDataSource(Ref ref) {
return InvoicesLocalDataSource();
}
/// Invoices Provider
///
/// Provides list of all invoices from local data source.
@riverpod
class Invoices extends _$Invoices {
@override
Future<List<InvoiceModel>> build() async {
return await ref.read(invoicesLocalDataSourceProvider).getAllInvoices();
}
/// Refresh invoices
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return await ref.read(invoicesLocalDataSourceProvider).getAllInvoices();
});
}
}
/// Selected Invoice Status Filter Provider
///
/// Tracks the currently selected invoice status filter for tabs.
/// null means "All" invoices.
@riverpod
class SelectedInvoiceStatusFilter extends _$SelectedInvoiceStatusFilter {
@override
String? build() {
return null; // Default: show all invoices
}
/// Select a status filter
void selectStatus(String? status) {
state = status;
}
/// Clear selection (show all)
void clearSelection() {
state = null;
}
}
/// Filtered Invoices Provider
///
/// Filters invoices by selected status tab.
@riverpod
Future<List<InvoiceModel>> filteredInvoices(Ref ref) async {
final invoicesAsync = ref.watch(invoicesProvider);
final selectedStatus = ref.watch(selectedInvoiceStatusFilterProvider);
return invoicesAsync.when(
data: (invoices) {
var filtered = invoices;
// Filter by status tab
if (selectedStatus != null) {
if (selectedStatus == 'unpaid') {
// Unpaid tab: issued status only
filtered = filtered
.where((invoice) => invoice.status == InvoiceStatus.issued && !invoice.isPaid)
.toList();
} else if (selectedStatus == 'overdue') {
// Overdue tab: overdue status
filtered = filtered
.where((invoice) => invoice.status == InvoiceStatus.overdue || invoice.isOverdue)
.toList();
} else if (selectedStatus == 'paid') {
// Paid tab: paid status
filtered = filtered
.where((invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid)
.toList();
}
}
// Sort by issue date (newest first)
filtered.sort((a, b) => b.issueDate.compareTo(a.issueDate));
return filtered;
},
loading: () => [],
error: (error, stack) => [],
);
}
/// Invoices Count by Status Provider
///
/// Returns count of invoices for each status tab.
@riverpod
Future<Map<String, int>> invoicesCountByStatus(Ref ref) async {
final invoicesAsync = ref.watch(invoicesProvider);
return invoicesAsync.when(
data: (invoices) {
final counts = <String, int>{};
// All tab
counts['all'] = invoices.length;
// Unpaid tab (issued status)
counts['unpaid'] = invoices
.where((invoice) => invoice.status == InvoiceStatus.issued && !invoice.isPaid)
.length;
// Overdue tab
counts['overdue'] = invoices
.where((invoice) => invoice.status == InvoiceStatus.overdue || invoice.isOverdue)
.length;
// Paid tab
counts['paid'] = invoices
.where((invoice) => invoice.status == InvoiceStatus.paid || invoice.isPaid)
.length;
return counts;
},
loading: () => {},
error: (error, stack) => {},
);
}
/// Total Invoices Amount Provider
///
/// Returns total amount of all invoices.
@riverpod
Future<double> totalInvoicesAmount(Ref ref) async {
final invoicesAsync = ref.watch(invoicesProvider);
return invoicesAsync.when(
data: (invoices) {
return invoices.fold<double>(0.0, (sum, invoice) => sum + invoice.totalAmount);
},
loading: () => 0.0,
error: (error, stack) => 0.0,
);
}
/// Total Unpaid Amount Provider
///
/// Returns total amount remaining across all unpaid invoices.
@riverpod
Future<double> totalUnpaidAmount(Ref ref) async {
final invoicesAsync = ref.watch(invoicesProvider);
return invoicesAsync.when(
data: (invoices) {
return invoices.fold<double>(0.0, (sum, invoice) => sum + invoice.amountRemaining);
},
loading: () => 0.0,
error: (error, stack) => 0.0,
);
}