feat: complete centralized error handling system with documentation

This commit is contained in:
2026-02-11 10:36:08 +05:30
parent 7570ffa3b9
commit 3e212220c7
43 changed files with 1144 additions and 2858 deletions

View File

@@ -6,7 +6,9 @@ import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
import 'package:krow_core/core.dart';
import '../../domain/repositories/payments_repository.dart';
class PaymentsRepositoryImpl implements PaymentsRepository {
class PaymentsRepositoryImpl
with dc.DataErrorHandler
implements PaymentsRepository {
PaymentsRepositoryImpl() : _dataConnect = dc.ExampleConnector.instance;
final dc.ExampleConnector _dataConnect;
@@ -27,17 +29,18 @@ class PaymentsRepositoryImpl implements PaymentsRepository {
// 3. Fetch from Data Connect using Firebase UID
final firebase_auth.User? user = _auth.currentUser;
if (user == null) {
throw Exception('User is not authenticated');
throw const NotAuthenticatedException(
technicalMessage: 'User is not authenticated',
);
}
try {
final QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> response = await _dataConnect.getStaffByUserId(userId: user.uid).execute();
if (response.data.staffs.isNotEmpty) {
_cachedStaffId = response.data.staffs.first.id;
return _cachedStaffId!;
}
} catch (e) {
// Log or handle error
// This call is protected by parent execution context if called within executeProtected,
// otherwise we might need to wrap it if called standalone.
// For now we assume it's called from public methods which are protected.
final QueryResult<dc.GetStaffByUserIdData, dc.GetStaffByUserIdVariables> response = await _dataConnect.getStaffByUserId(userId: user.uid).execute();
if (response.data.staffs.isNotEmpty) {
_cachedStaffId = response.data.staffs.first.id;
return _cachedStaffId!;
}
// 4. Fallback
@@ -78,55 +81,57 @@ class PaymentsRepositoryImpl implements PaymentsRepository {
@override
Future<PaymentSummary> getPaymentSummary() async {
final String currentStaffId = await _getStaffId();
return executeProtected(() async {
final String currentStaffId = await _getStaffId();
// Fetch recent payments with a limit
// Note: limit is chained on the query builder
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> result =
await _dataConnect.listRecentPaymentsByStaffId(
staffId: currentStaffId,
).limit(100).execute();
// Fetch recent payments with a limit
// Note: limit is chained on the query builder
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> result =
await _dataConnect.listRecentPaymentsByStaffId(
staffId: currentStaffId,
).limit(100).execute();
final List<dc.ListRecentPaymentsByStaffIdRecentPayments> payments = result.data.recentPayments;
final List<dc.ListRecentPaymentsByStaffIdRecentPayments> payments = result.data.recentPayments;
double weekly = 0;
double monthly = 0;
double pending = 0;
double total = 0;
double weekly = 0;
double monthly = 0;
double pending = 0;
double total = 0;
final DateTime now = DateTime.now();
final DateTime startOfWeek = now.subtract(const Duration(days: 7));
final DateTime startOfMonth = DateTime(now.year, now.month, 1);
final DateTime now = DateTime.now();
final DateTime startOfWeek = now.subtract(const Duration(days: 7));
final DateTime startOfMonth = DateTime(now.year, now.month, 1);
for (final dc.ListRecentPaymentsByStaffIdRecentPayments p in payments) {
final DateTime? date = _toDateTime(p.invoice.issueDate) ?? _toDateTime(p.createdAt);
final double amount = p.invoice.amount;
final String? status = p.status?.stringValue;
for (final dc.ListRecentPaymentsByStaffIdRecentPayments p in payments) {
final DateTime? date = _toDateTime(p.invoice.issueDate) ?? _toDateTime(p.createdAt);
final double amount = p.invoice.amount;
final String? status = p.status?.stringValue;
if (status == 'PENDING') {
pending += amount;
} else if (status == 'PAID') {
total += amount;
if (date != null) {
if (date.isAfter(startOfWeek)) weekly += amount;
if (date.isAfter(startOfMonth)) monthly += amount;
}
}
}
if (status == 'PENDING') {
pending += amount;
} else if (status == 'PAID') {
total += amount;
if (date != null) {
if (date.isAfter(startOfWeek)) weekly += amount;
if (date.isAfter(startOfMonth)) monthly += amount;
}
}
}
return PaymentSummary(
weeklyEarnings: weekly,
monthlyEarnings: monthly,
pendingEarnings: pending,
totalEarnings: total,
);
return PaymentSummary(
weeklyEarnings: weekly,
monthlyEarnings: monthly,
pendingEarnings: pending,
totalEarnings: total,
);
});
}
@override
Future<List<StaffPayment>> getPaymentHistory(String period) async {
final String currentStaffId = await _getStaffId();
try {
return executeProtected(() async {
final String currentStaffId = await _getStaffId();
final QueryResult<dc.ListRecentPaymentsByStaffIdData, dc.ListRecentPaymentsByStaffIdVariables> response =
await _dataConnect
.listRecentPaymentsByStaffId(staffId: currentStaffId)
@@ -142,9 +147,7 @@ class PaymentsRepositoryImpl implements PaymentsRepository {
paidAt: _toDateTime(payment.invoice.issueDate),
);
}).toList();
} catch (e) {
return <StaffPayment>[];
}
});
}
}

View File

@@ -4,6 +4,7 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:core_localization/core_localization.dart';
import '../blocs/payments/payments_bloc.dart';
import '../blocs/payments/payments_event.dart';
import '../blocs/payments/payments_state.dart';
@@ -30,16 +31,37 @@ class _PaymentsPageState extends State<PaymentsPage> {
@override
Widget build(BuildContext context) {
Translations.of(context);
return BlocProvider<PaymentsBloc>.value(
value: _bloc,
child: Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
body: BlocBuilder<PaymentsBloc, PaymentsState>(
body: BlocConsumer<PaymentsBloc, PaymentsState>(
listener: (context, state) {
if (state is PaymentsError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(translateErrorKey(state.message)),
behavior: SnackBarBehavior.floating,
),
);
}
},
builder: (BuildContext context, PaymentsState state) {
if (state is PaymentsLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is PaymentsError) {
return Center(child: Text('Error: ${state.message}'));
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
translateErrorKey(state.message),
textAlign: TextAlign.center,
style: const TextStyle(color: Color(0xFF64748B)), // TextSecondary
),
),
);
} else if (state is PaymentsLoaded) {
return _buildContent(context, state);
}