feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/core/data/enums/pagination_status.dart';
|
||||
import 'package:krow/core/data/enums/state_status.dart';
|
||||
import 'package:krow/features/earning/domain/entities/earning_shift_entity.dart';
|
||||
import 'package:krow/features/earning/domain/entities/earnings_summary_entity.dart';
|
||||
import 'package:krow/features/earning/domain/staff_earning_repository.dart';
|
||||
|
||||
part 'earnings_data.dart';
|
||||
part 'earnings_event.dart';
|
||||
part 'earnings_state.dart';
|
||||
|
||||
class EarningsBloc extends Bloc<EarningsEvent, EarningsState> {
|
||||
EarningsBloc()
|
||||
: super(const EarningsState.initial()
|
||||
..copyWith(
|
||||
tabs: defaultEarningTabs,
|
||||
)) {
|
||||
on<EarningsInitEvent>(_onInit);
|
||||
on<EarningsSwitchBalancePeriodEvent>(_onSwitchBalancePeriod);
|
||||
on<EarningsTabChangedEvent>(_onTabChanged);
|
||||
on<LoadAdditionalEarnings>(_onLoadAdditionalEarnings);
|
||||
on<ReloadCurrentEarnings>(_onReloadCurrentEarnings);
|
||||
on<ConfirmStaffEarning>(_onConfirmStaffEarning);
|
||||
on<DisputeStaffEarning>(_onDisputeStaffEarning);
|
||||
}
|
||||
|
||||
final _earningRepository = getIt<StaffEarningRepository>();
|
||||
|
||||
Future<void> _onInit(
|
||||
EarningsInitEvent event,
|
||||
Emitter<EarningsState> emit,
|
||||
) async {
|
||||
add(const LoadAdditionalEarnings());
|
||||
|
||||
emit(state.copyWith(status: StateStatus.loading));
|
||||
EarningsSummaryEntity? earningsData;
|
||||
try {
|
||||
earningsData = await _earningRepository.getStaffEarningsData();
|
||||
} catch (except) {
|
||||
log('Error in EarningsBloc, on EarningsInitEvent', error: except);
|
||||
}
|
||||
emit(state.copyWith(earnings: earningsData, status: StateStatus.idle));
|
||||
}
|
||||
|
||||
void _onSwitchBalancePeriod(
|
||||
EarningsSwitchBalancePeriodEvent event,
|
||||
Emitter<EarningsState> emit,
|
||||
) {
|
||||
emit(state.copyWith(balancePeriod: event.period));
|
||||
}
|
||||
|
||||
void _onTabChanged(
|
||||
EarningsTabChangedEvent event,
|
||||
Emitter<EarningsState> emit,
|
||||
) {
|
||||
emit(state.copyWith(tabIndex: event.tabIndex));
|
||||
|
||||
if (state.currentTab.status == PaginationStatus.initial) {
|
||||
add(const LoadAdditionalEarnings());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onLoadAdditionalEarnings(
|
||||
LoadAdditionalEarnings event,
|
||||
Emitter<EarningsState> emit,
|
||||
) async {
|
||||
if (!state.currentTab.status.allowLoad) return;
|
||||
emit(state.updateCurrentTab(status: PaginationStatus.loading));
|
||||
|
||||
final tabIndex = state.tabIndex;
|
||||
try {
|
||||
final earningsBatch = await _earningRepository.getEarningsBatch(
|
||||
status: state.currentTab.loadingKey,
|
||||
lastEntryCursor: state.currentTab.paginationCursor,
|
||||
);
|
||||
|
||||
emit(
|
||||
state.copyWithTab(
|
||||
tabIndex: tabIndex,
|
||||
tab: state.tabs[tabIndex].copyWith(
|
||||
status: earningsBatch.hasNextBatch
|
||||
? PaginationStatus.idle
|
||||
: PaginationStatus.end,
|
||||
items: [...state.tabs[tabIndex].items, ...earningsBatch.earnings],
|
||||
hasMoreItems: earningsBatch.hasNextBatch,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (except) {
|
||||
log(
|
||||
'Error in EarningsBloc, on LoadAdditionalEarnings',
|
||||
error: except,
|
||||
);
|
||||
emit(
|
||||
state.copyWithTab(
|
||||
tabIndex: tabIndex,
|
||||
tab: state.tabs[tabIndex].copyWith(
|
||||
status: PaginationStatus.error,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.tabs[tabIndex].status == PaginationStatus.loading) {
|
||||
emit(
|
||||
state.copyWithTab(
|
||||
tabIndex: tabIndex,
|
||||
tab: state.tabs[tabIndex].copyWith(
|
||||
status: PaginationStatus.idle,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onReloadCurrentEarnings(
|
||||
ReloadCurrentEarnings event,
|
||||
Emitter<EarningsState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWithTab(
|
||||
tab: EarningsTabState.initial(
|
||||
label: state.currentTab.label,
|
||||
loadingKey: state.currentTab.loadingKey,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
add(const LoadAdditionalEarnings());
|
||||
}
|
||||
|
||||
Future<void> _onEarningAction(
|
||||
Future<EarningShiftEntity?> earningFuture,
|
||||
String eventName,
|
||||
Emitter<EarningsState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(status: StateStatus.loading));
|
||||
|
||||
EarningShiftEntity? earning;
|
||||
try {
|
||||
earning = await earningFuture;
|
||||
} catch (except) {
|
||||
log(
|
||||
'Error in EarningsBloc, on $eventName',
|
||||
error: except,
|
||||
);
|
||||
}
|
||||
|
||||
var tabData = state.currentTab;
|
||||
if (earning != null) {
|
||||
final earningIndex =
|
||||
state.currentTab.items.indexWhere((item) => item.id == earning?.id);
|
||||
|
||||
if (earningIndex >= 0) {
|
||||
tabData = state.currentTab.copyWith(
|
||||
items: List.from(state.currentTab.items)..[earningIndex] = earning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWithTab(
|
||||
status: StateStatus.idle,
|
||||
tab: tabData,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onConfirmStaffEarning(
|
||||
ConfirmStaffEarning event,
|
||||
Emitter<EarningsState> emit,
|
||||
) {
|
||||
return _onEarningAction(
|
||||
_earningRepository.confirmStaffEarning(
|
||||
earningId: event.earningId,
|
||||
),
|
||||
'ConfirmStaffEarning',
|
||||
emit,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onDisputeStaffEarning(
|
||||
DisputeStaffEarning event,
|
||||
Emitter<EarningsState> emit,
|
||||
) async {
|
||||
return _onEarningAction(
|
||||
_earningRepository.disputeStaffEarning(
|
||||
id: event.earningId,
|
||||
reason: event.reason,
|
||||
details: event.details,
|
||||
),
|
||||
'DeclineStaffEarning',
|
||||
emit,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
part of 'earnings_bloc.dart';
|
||||
|
||||
enum BalancePeriod {
|
||||
week,
|
||||
month,
|
||||
}
|
||||
|
||||
class EarningsTabState {
|
||||
const EarningsTabState({
|
||||
required this.label,
|
||||
required this.loadingKey,
|
||||
required this.items,
|
||||
this.status = PaginationStatus.idle,
|
||||
this.hasMoreItems = true,
|
||||
this.paginationCursor,
|
||||
});
|
||||
|
||||
const EarningsTabState.initial({
|
||||
required this.label,
|
||||
required this.loadingKey,
|
||||
this.items = const [],
|
||||
this.status = PaginationStatus.initial,
|
||||
this.hasMoreItems = true,
|
||||
this.paginationCursor,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final String loadingKey;
|
||||
final List<EarningShiftEntity> items;
|
||||
final PaginationStatus status;
|
||||
final bool hasMoreItems;
|
||||
final String? paginationCursor;
|
||||
|
||||
EarningsTabState copyWith({
|
||||
List<EarningShiftEntity>? items,
|
||||
PaginationStatus? status,
|
||||
bool? hasMoreItems,
|
||||
String? label,
|
||||
String? loadingKey,
|
||||
String? paginationCursor,
|
||||
}) {
|
||||
return EarningsTabState(
|
||||
loadingKey: loadingKey ?? this.loadingKey,
|
||||
items: items ?? this.items,
|
||||
status: status ?? this.status,
|
||||
hasMoreItems: hasMoreItems ?? this.hasMoreItems,
|
||||
label: label ?? this.label,
|
||||
paginationCursor: paginationCursor ?? this.paginationCursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const defaultEarningTabs = [
|
||||
EarningsTabState.initial(
|
||||
label: 'new_earning',
|
||||
loadingKey: 'new' // Confirmed by admin
|
||||
),
|
||||
EarningsTabState.initial(
|
||||
label: 'confirmed',
|
||||
loadingKey: 'confirmed', // Confirmed by staff
|
||||
),
|
||||
EarningsTabState.initial(
|
||||
label: 'disputed',
|
||||
loadingKey: 'disputed', // Declined by staff
|
||||
),
|
||||
EarningsTabState.initial(
|
||||
label: 'sent',
|
||||
loadingKey: 'sent', // Should remain sent
|
||||
),
|
||||
EarningsTabState.initial(
|
||||
label: 'received',
|
||||
loadingKey: 'paid', // Should remain paid
|
||||
),
|
||||
];
|
||||
|
||||
// $of = match ($status) {
|
||||
// 'new' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::new]),
|
||||
// 'confirmed' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::confirmed_by_admin]),
|
||||
// 'pending' => fn(Builder $q) => $q->whereIn('status', [
|
||||
// StaffPaymentStatus::decline_by_staff,
|
||||
// StaffPaymentStatus::confirmed_by_staff,
|
||||
// ]),
|
||||
// 'sent' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::sent]),
|
||||
// 'paid' => fn(Builder $q) => $q->whereIn('status', [StaffPaymentStatus::paid]),
|
||||
// };
|
||||
@@ -0,0 +1,48 @@
|
||||
part of 'earnings_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class EarningsEvent {
|
||||
const EarningsEvent();
|
||||
}
|
||||
|
||||
class EarningsInitEvent extends EarningsEvent {
|
||||
const EarningsInitEvent();
|
||||
}
|
||||
|
||||
class EarningsSwitchBalancePeriodEvent extends EarningsEvent {
|
||||
final BalancePeriod period;
|
||||
|
||||
const EarningsSwitchBalancePeriodEvent({required this.period});
|
||||
}
|
||||
|
||||
class EarningsTabChangedEvent extends EarningsEvent {
|
||||
final int tabIndex;
|
||||
|
||||
const EarningsTabChangedEvent({required this.tabIndex});
|
||||
}
|
||||
|
||||
class LoadAdditionalEarnings extends EarningsEvent {
|
||||
const LoadAdditionalEarnings();
|
||||
}
|
||||
|
||||
class ReloadCurrentEarnings extends EarningsEvent {
|
||||
const ReloadCurrentEarnings();
|
||||
}
|
||||
|
||||
class ConfirmStaffEarning extends EarningsEvent {
|
||||
const ConfirmStaffEarning(this.earningId);
|
||||
|
||||
final String earningId;
|
||||
}
|
||||
|
||||
class DisputeStaffEarning extends EarningsEvent {
|
||||
const DisputeStaffEarning({
|
||||
required this.earningId,
|
||||
required this.reason,
|
||||
required this.details,
|
||||
});
|
||||
|
||||
final String earningId;
|
||||
final String reason;
|
||||
final String details;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
part of 'earnings_bloc.dart';
|
||||
|
||||
@immutable
|
||||
class EarningsState {
|
||||
const EarningsState({
|
||||
required this.status,
|
||||
required this.tabIndex,
|
||||
required this.balancePeriod,
|
||||
required this.earnings,
|
||||
required this.tabs,
|
||||
});
|
||||
|
||||
const EarningsState.initial({
|
||||
this.status = StateStatus.idle,
|
||||
this.tabIndex = 0,
|
||||
this.balancePeriod = BalancePeriod.week,
|
||||
this.earnings = const EarningsSummaryEntity.empty(),
|
||||
this.tabs = defaultEarningTabs,
|
||||
});
|
||||
|
||||
final StateStatus status;
|
||||
final int tabIndex;
|
||||
final BalancePeriod balancePeriod;
|
||||
final EarningsSummaryEntity earnings;
|
||||
final List<EarningsTabState> tabs;
|
||||
|
||||
EarningsTabState get currentTab => tabs[tabIndex];
|
||||
|
||||
double get totalEarnings {
|
||||
return balancePeriod == BalancePeriod.week
|
||||
? earnings.totalEarningsByWeek
|
||||
: earnings.totalEarningsByMonth;
|
||||
}
|
||||
|
||||
double get totalHours {
|
||||
return balancePeriod == BalancePeriod.week
|
||||
? earnings.totalWorkedHoursByWeek
|
||||
: earnings.totalWorkedHoursByMonth;
|
||||
}
|
||||
|
||||
double get totalPayout {
|
||||
return balancePeriod == BalancePeriod.week
|
||||
? earnings.payoutByWeek
|
||||
: earnings.payoutByMonth;
|
||||
}
|
||||
|
||||
EarningsState copyWith({
|
||||
StateStatus? status,
|
||||
int? tabIndex,
|
||||
BalancePeriod? balancePeriod,
|
||||
EarningsSummaryEntity? earnings,
|
||||
List<EarningsTabState>? tabs,
|
||||
}) {
|
||||
return EarningsState(
|
||||
status: status ?? this.status,
|
||||
tabIndex: tabIndex ?? this.tabIndex,
|
||||
balancePeriod: balancePeriod ?? this.balancePeriod,
|
||||
earnings: earnings ?? this.earnings,
|
||||
tabs: tabs ?? this.tabs,
|
||||
);
|
||||
}
|
||||
|
||||
EarningsState copyWithTab({
|
||||
required EarningsTabState tab,
|
||||
int? tabIndex,
|
||||
StateStatus? status,
|
||||
}) {
|
||||
return copyWith(
|
||||
tabs: List.from(tabs)..[tabIndex ?? this.tabIndex] = tab,
|
||||
status: status,
|
||||
);
|
||||
}
|
||||
|
||||
EarningsState updateCurrentTab({
|
||||
List<EarningShiftEntity>? items,
|
||||
PaginationStatus? status,
|
||||
bool? hasMoreItems,
|
||||
String? label,
|
||||
String? paginationCursor,
|
||||
}) {
|
||||
return copyWith(
|
||||
tabs: List.from(tabs)
|
||||
..[tabIndex] = currentTab.copyWith(
|
||||
items: items,
|
||||
status: status,
|
||||
hasMoreItems: hasMoreItems,
|
||||
label: label,
|
||||
paginationCursor: paginationCursor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
class EarningDisputeInfo {
|
||||
String reason = '';
|
||||
String details = '';
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
class EarningShiftEntity {
|
||||
final String id;
|
||||
final EarningStatus status;
|
||||
final String businessImageUrl;
|
||||
final String skillName;
|
||||
final String businessName;
|
||||
final int? totalBreakTime;
|
||||
final int? paymentStatus;
|
||||
final double? earned;
|
||||
final DateTime? clockIn;
|
||||
final DateTime? clockOut;
|
||||
final String eventName;
|
||||
final DateTime eventDate;
|
||||
|
||||
EarningShiftEntity({
|
||||
required this.id,
|
||||
required this.status,
|
||||
required this.businessImageUrl,
|
||||
required this.skillName,
|
||||
required this.businessName,
|
||||
required this.totalBreakTime,
|
||||
required this.paymentStatus,
|
||||
required this.earned,
|
||||
required this.clockIn,
|
||||
required this.clockOut,
|
||||
required this.eventName,
|
||||
required this.eventDate,
|
||||
});
|
||||
|
||||
int get paymentProgressStep {
|
||||
return switch (status) {
|
||||
EarningStatus.pending => 0, // corresponds to Pending Sent
|
||||
EarningStatus.processing => 1, // corresponds to Pending Payment
|
||||
EarningStatus.paid => 2, // corresponds to Payment Received
|
||||
_ => -1, // don't show active step
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum EarningStatus {
|
||||
new_,
|
||||
pending,
|
||||
confirmedByAdmin,
|
||||
confirmedByStaff,
|
||||
declineByStaff,
|
||||
processing,
|
||||
paid,
|
||||
failed,
|
||||
canceled;
|
||||
|
||||
static EarningStatus fromString(value) {
|
||||
return switch (value) {
|
||||
'new' => new_,
|
||||
'confirmed_by_admin' => confirmedByAdmin,
|
||||
'confirmed_by_staff' => confirmedByStaff,
|
||||
'decline_by_staff' => declineByStaff,
|
||||
'pending' => pending,
|
||||
'paid' => paid,
|
||||
'failed' => failed,
|
||||
'canceled' => canceled,
|
||||
_ => canceled,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:krow/features/earning/domain/entities/earning_shift_entity.dart';
|
||||
|
||||
class EarningsBatchEntity {
|
||||
EarningsBatchEntity({
|
||||
required this.batchStatus,
|
||||
required this.hasNextBatch,
|
||||
required this.earnings,
|
||||
this.cursor,
|
||||
});
|
||||
|
||||
final String batchStatus;
|
||||
final bool hasNextBatch;
|
||||
final List<EarningShiftEntity> earnings;
|
||||
final String? cursor;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
@immutable
|
||||
class EarningsSummaryEntity {
|
||||
const EarningsSummaryEntity({
|
||||
required this.totalEarningsByWeek,
|
||||
required this.totalEarningsByMonth,
|
||||
required this.totalWorkedHoursByWeek,
|
||||
required this.totalWorkedHoursByMonth,
|
||||
required this.payoutByWeek,
|
||||
required this.payoutByMonth,
|
||||
required this.startDatePeriod,
|
||||
required this.endDatePeriod,
|
||||
required this.maxEarningInPeriod,
|
||||
required this.minEarningInPeriod,
|
||||
});
|
||||
|
||||
const EarningsSummaryEntity.empty({
|
||||
this.totalEarningsByWeek = 0,
|
||||
this.totalEarningsByMonth = 0,
|
||||
this.totalWorkedHoursByWeek = 0,
|
||||
this.totalWorkedHoursByMonth = 0,
|
||||
this.payoutByWeek = 0,
|
||||
this.payoutByMonth = 0,
|
||||
this.startDatePeriod,
|
||||
this.endDatePeriod,
|
||||
this.maxEarningInPeriod = 0,
|
||||
this.minEarningInPeriod = 0,
|
||||
});
|
||||
|
||||
final double totalEarningsByWeek;
|
||||
final double totalEarningsByMonth;
|
||||
final double totalWorkedHoursByWeek;
|
||||
final double totalWorkedHoursByMonth;
|
||||
final double payoutByWeek;
|
||||
final double payoutByMonth;
|
||||
final DateTime? startDatePeriod;
|
||||
final DateTime? endDatePeriod;
|
||||
final int maxEarningInPeriod;
|
||||
final int minEarningInPeriod;
|
||||
|
||||
EarningsSummaryEntity copyWith({
|
||||
double? totalEarningsByWeek,
|
||||
double? totalEarningsByMonth,
|
||||
double? totalWorkedHoursByWeek,
|
||||
double? totalWorkedHoursByMonth,
|
||||
double? payoutByWeek,
|
||||
double? payoutByMonth,
|
||||
DateTime? startDatePeriod,
|
||||
DateTime? endDatePeriod,
|
||||
int? maxEarningInPeriod,
|
||||
int? minEarningInPeriod,
|
||||
}) {
|
||||
return EarningsSummaryEntity(
|
||||
totalEarningsByWeek: totalEarningsByWeek ?? this.totalEarningsByWeek,
|
||||
totalEarningsByMonth: totalEarningsByMonth ?? this.totalEarningsByMonth,
|
||||
totalWorkedHoursByWeek:
|
||||
totalWorkedHoursByWeek ?? this.totalWorkedHoursByWeek,
|
||||
totalWorkedHoursByMonth:
|
||||
totalWorkedHoursByMonth ?? this.totalWorkedHoursByMonth,
|
||||
payoutByWeek: payoutByWeek ?? this.payoutByWeek,
|
||||
payoutByMonth: payoutByMonth ?? this.payoutByMonth,
|
||||
startDatePeriod: startDatePeriod,
|
||||
endDatePeriod: endDatePeriod,
|
||||
maxEarningInPeriod: maxEarningInPeriod ?? this.maxEarningInPeriod,
|
||||
minEarningInPeriod: minEarningInPeriod ?? this.minEarningInPeriod,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:krow/features/earning/domain/entities/earning_shift_entity.dart';
|
||||
import 'package:krow/features/earning/domain/entities/earnings_batch_entity.dart';
|
||||
import 'package:krow/features/earning/domain/entities/earnings_summary_entity.dart';
|
||||
|
||||
abstract interface class StaffEarningRepository {
|
||||
Future<EarningsSummaryEntity> getStaffEarningsData();
|
||||
|
||||
Future<EarningsBatchEntity> getEarningsBatch({
|
||||
required String status,
|
||||
int limit = 10,
|
||||
String? lastEntryCursor,
|
||||
});
|
||||
|
||||
Future<EarningShiftEntity?> confirmStaffEarning({required String earningId});
|
||||
|
||||
Future<EarningShiftEntity?> disputeStaffEarning({
|
||||
required String id,
|
||||
required String reason,
|
||||
String? details,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user