Fix: Resolve critical linting issues and bugs (concurrency, syntax, dead code)

This commit is contained in:
2026-02-10 19:12:01 +05:30
parent 5e7bf0d5c0
commit 7570ffa3b9
46 changed files with 4057 additions and 1299 deletions

View File

@@ -1,28 +1,38 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../../domain/usecases/get_certificates_usecase.dart';
import 'certificates_state.dart';
class CertificatesCubit extends Cubit<CertificatesState> {
class CertificatesCubit extends Cubit<CertificatesState>
with BlocErrorHandler<CertificatesState> {
final GetCertificatesUseCase _getCertificatesUseCase;
CertificatesCubit(this._getCertificatesUseCase) : super(const CertificatesState()) {
CertificatesCubit(this._getCertificatesUseCase)
: super(const CertificatesState()) {
loadCertificates();
}
Future<void> loadCertificates() async {
emit(state.copyWith(status: CertificatesStatus.loading));
try {
final List<StaffDocument> certificates = await _getCertificatesUseCase();
emit(state.copyWith(
status: CertificatesStatus.success,
certificates: certificates,
));
} catch (e) {
emit(state.copyWith(
status: CertificatesStatus.failure,
errorMessage: e.toString(),
));
}
await handleError(
emit: emit,
action: () async {
final List<StaffDocument> certificates =
await _getCertificatesUseCase();
emit(
state.copyWith(
status: CertificatesStatus.success,
certificates: certificates,
),
);
},
onError:
(String errorKey) => state.copyWith(
status: CertificatesStatus.failure,
errorMessage: errorKey,
),
);
}
}

View File

@@ -49,6 +49,7 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
),
];
/*
try {
final QueryResult<ListStaffDocumentsByStaffIdData,
ListStaffDocumentsByStaffIdVariables> result =
@@ -63,6 +64,7 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
} catch (e) {
throw Exception('Failed to fetch documents: $e');
}
*/
}
domain.StaffDocument _mapToDomain(

View File

@@ -1,26 +1,34 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../../domain/usecases/get_documents_usecase.dart';
import 'documents_state.dart';
class DocumentsCubit extends Cubit<DocumentsState> {
class DocumentsCubit extends Cubit<DocumentsState>
with BlocErrorHandler<DocumentsState> {
final GetDocumentsUseCase _getDocumentsUseCase;
DocumentsCubit(this._getDocumentsUseCase) : super(const DocumentsState());
Future<void> loadDocuments() async {
emit(state.copyWith(status: DocumentsStatus.loading));
try {
final List<StaffDocument> documents = await _getDocumentsUseCase();
emit(state.copyWith(
status: DocumentsStatus.success,
documents: documents,
));
} catch (e) {
emit(state.copyWith(
status: DocumentsStatus.failure,
errorMessage: e.toString(),
));
}
await handleError(
emit: emit,
action: () async {
final List<StaffDocument> documents = await _getDocumentsUseCase();
emit(
state.copyWith(
status: DocumentsStatus.success,
documents: documents,
),
);
},
onError:
(String errorKey) => state.copyWith(
status: DocumentsStatus.failure,
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,11 +1,12 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:uuid/uuid.dart';
import '../../../domain/usecases/submit_i9_form_usecase.dart';
import 'form_i9_state.dart';
class FormI9Cubit extends Cubit<FormI9State> {
class FormI9Cubit extends Cubit<FormI9State> with BlocErrorHandler<FormI9State> {
final SubmitI9FormUseCase _submitI9FormUseCase;
String _formId = '';
@@ -16,31 +17,33 @@ class FormI9Cubit extends Cubit<FormI9State> {
emit(const FormI9State()); // Reset to empty if no form
return;
}
final Map<String, dynamic> data = form.formData;
_formId = form.id;
emit(FormI9State(
firstName: data['firstName'] as String? ?? '',
lastName: data['lastName'] as String? ?? '',
middleInitial: data['middleInitial'] as String? ?? '',
otherLastNames: data['otherLastNames'] as String? ?? '',
dob: data['dob'] as String? ?? '',
ssn: data['ssn'] as String? ?? '',
email: data['email'] as String? ?? '',
phone: data['phone'] as String? ?? '',
address: data['address'] as String? ?? '',
aptNumber: data['aptNumber'] as String? ?? '',
city: data['city'] as String? ?? '',
state: data['state'] as String? ?? '',
zipCode: data['zipCode'] as String? ?? '',
citizenshipStatus: data['citizenshipStatus'] as String? ?? '',
uscisNumber: data['uscisNumber'] as String? ?? '',
admissionNumber: data['admissionNumber'] as String? ?? '',
passportNumber: data['passportNumber'] as String? ?? '',
countryIssuance: data['countryIssuance'] as String? ?? '',
preparerUsed: data['preparerUsed'] as bool? ?? false,
signature: data['signature'] as String? ?? '',
));
emit(
FormI9State(
firstName: data['firstName'] as String? ?? '',
lastName: data['lastName'] as String? ?? '',
middleInitial: data['middleInitial'] as String? ?? '',
otherLastNames: data['otherLastNames'] as String? ?? '',
dob: data['dob'] as String? ?? '',
ssn: data['ssn'] as String? ?? '',
email: data['email'] as String? ?? '',
phone: data['phone'] as String? ?? '',
address: data['address'] as String? ?? '',
aptNumber: data['aptNumber'] as String? ?? '',
city: data['city'] as String? ?? '',
state: data['state'] as String? ?? '',
zipCode: data['zipCode'] as String? ?? '',
citizenshipStatus: data['citizenshipStatus'] as String? ?? '',
uscisNumber: data['uscisNumber'] as String? ?? '',
admissionNumber: data['admissionNumber'] as String? ?? '',
passportNumber: data['passportNumber'] as String? ?? '',
countryIssuance: data['countryIssuance'] as String? ?? '',
preparerUsed: data['preparerUsed'] as bool? ?? false,
signature: data['signature'] as String? ?? '',
),
);
}
void nextStep(int totalSteps) {
@@ -58,8 +61,10 @@ class FormI9Cubit extends Cubit<FormI9State> {
// Personal Info
void firstNameChanged(String value) => emit(state.copyWith(firstName: value));
void lastNameChanged(String value) => emit(state.copyWith(lastName: value));
void middleInitialChanged(String value) => emit(state.copyWith(middleInitial: value));
void otherLastNamesChanged(String value) => emit(state.copyWith(otherLastNames: value));
void middleInitialChanged(String value) =>
emit(state.copyWith(middleInitial: value));
void otherLastNamesChanged(String value) =>
emit(state.copyWith(otherLastNames: value));
void dobChanged(String value) => emit(state.copyWith(dob: value));
void ssnChanged(String value) => emit(state.copyWith(ssn: value));
void emailChanged(String value) => emit(state.copyWith(email: value));
@@ -73,55 +78,65 @@ class FormI9Cubit extends Cubit<FormI9State> {
void zipCodeChanged(String value) => emit(state.copyWith(zipCode: value));
// Citizenship
void citizenshipStatusChanged(String value) => emit(state.copyWith(citizenshipStatus: value));
void uscisNumberChanged(String value) => emit(state.copyWith(uscisNumber: value));
void admissionNumberChanged(String value) => emit(state.copyWith(admissionNumber: value));
void passportNumberChanged(String value) => emit(state.copyWith(passportNumber: value));
void countryIssuanceChanged(String value) => emit(state.copyWith(countryIssuance: value));
void citizenshipStatusChanged(String value) =>
emit(state.copyWith(citizenshipStatus: value));
void uscisNumberChanged(String value) =>
emit(state.copyWith(uscisNumber: value));
void admissionNumberChanged(String value) =>
emit(state.copyWith(admissionNumber: value));
void passportNumberChanged(String value) =>
emit(state.copyWith(passportNumber: value));
void countryIssuanceChanged(String value) =>
emit(state.copyWith(countryIssuance: value));
// Signature
void preparerUsedChanged(bool value) => emit(state.copyWith(preparerUsed: value));
void preparerUsedChanged(bool value) =>
emit(state.copyWith(preparerUsed: value));
void signatureChanged(String value) => emit(state.copyWith(signature: value));
Future<void> submit() async {
emit(state.copyWith(status: FormI9Status.submitting));
try {
final Map<String, dynamic> formData = {
'firstName': state.firstName,
'lastName': state.lastName,
'middleInitial': state.middleInitial,
'otherLastNames': state.otherLastNames,
'dob': state.dob,
'ssn': state.ssn,
'email': state.email,
'phone': state.phone,
'address': state.address,
'aptNumber': state.aptNumber,
'city': state.city,
'state': state.state,
'zipCode': state.zipCode,
'citizenshipStatus': state.citizenshipStatus,
'uscisNumber': state.uscisNumber,
'admissionNumber': state.admissionNumber,
'passportNumber': state.passportNumber,
'countryIssuance': state.countryIssuance,
'preparerUsed': state.preparerUsed,
'signature': state.signature,
};
await handleError(
emit: emit,
action: () async {
final Map<String, dynamic> formData = {
'firstName': state.firstName,
'lastName': state.lastName,
'middleInitial': state.middleInitial,
'otherLastNames': state.otherLastNames,
'dob': state.dob,
'ssn': state.ssn,
'email': state.email,
'phone': state.phone,
'address': state.address,
'aptNumber': state.aptNumber,
'city': state.city,
'state': state.state,
'zipCode': state.zipCode,
'citizenshipStatus': state.citizenshipStatus,
'uscisNumber': state.uscisNumber,
'admissionNumber': state.admissionNumber,
'passportNumber': state.passportNumber,
'countryIssuance': state.countryIssuance,
'preparerUsed': state.preparerUsed,
'signature': state.signature,
};
final I9TaxForm form = I9TaxForm(
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
title: 'Form I-9',
formData: formData,
);
final I9TaxForm form = I9TaxForm(
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
title: 'Form I-9',
formData: formData,
);
await _submitI9FormUseCase(form);
emit(state.copyWith(status: FormI9Status.success));
} catch (e) {
emit(state.copyWith(
status: FormI9Status.failure,
errorMessage: e.toString(),
));
}
await _submitI9FormUseCase(form);
emit(state.copyWith(status: FormI9Status.success));
},
onError:
(String errorKey) => state.copyWith(
status: FormI9Status.failure,
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,26 +1,29 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../../domain/usecases/get_tax_forms_usecase.dart';
import 'tax_forms_state.dart';
class TaxFormsCubit extends Cubit<TaxFormsState> {
class TaxFormsCubit extends Cubit<TaxFormsState>
with BlocErrorHandler<TaxFormsState> {
final GetTaxFormsUseCase _getTaxFormsUseCase;
TaxFormsCubit(this._getTaxFormsUseCase) : super(const TaxFormsState());
Future<void> loadTaxForms() async {
emit(state.copyWith(status: TaxFormsStatus.loading));
try {
final List<TaxForm> forms = await _getTaxFormsUseCase();
emit(state.copyWith(
status: TaxFormsStatus.success,
forms: forms,
));
} catch (e) {
emit(state.copyWith(
status: TaxFormsStatus.failure,
errorMessage: e.toString(),
));
}
await handleError(
emit: emit,
action: () async {
final List<TaxForm> forms = await _getTaxFormsUseCase();
emit(state.copyWith(status: TaxFormsStatus.success, forms: forms));
},
onError:
(String errorKey) => state.copyWith(
status: TaxFormsStatus.failure,
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,11 +1,12 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:uuid/uuid.dart';
import '../../../domain/usecases/submit_w4_form_usecase.dart';
import 'form_w4_state.dart';
class FormW4Cubit extends Cubit<FormW4State> {
class FormW4Cubit extends Cubit<FormW4State> with BlocErrorHandler<FormW4State> {
final SubmitW4FormUseCase _submitW4FormUseCase;
String _formId = '';
@@ -16,31 +17,33 @@ class FormW4Cubit extends Cubit<FormW4State> {
emit(const FormW4State()); // Reset
return;
}
final Map<String, dynamic> data = form.formData;
_formId = form.id;
// Combine address parts if needed, or take existing
final String city = data['city'] as String? ?? '';
final String stateVal = data['state'] as String? ?? '';
final String zip = data['zipCode'] as String? ?? '';
final String cityStateZip = '$city, $stateVal $zip'.trim();
emit(FormW4State(
firstName: data['firstName'] as String? ?? '',
lastName: data['lastName'] as String? ?? '',
ssn: data['ssn'] as String? ?? '',
address: data['address'] as String? ?? '',
cityStateZip: cityStateZip.contains(',') ? cityStateZip : '',
filingStatus: data['filingStatus'] as String? ?? '',
multipleJobs: data['multipleJobs'] as bool? ?? false,
qualifyingChildren: data['qualifyingChildren'] as int? ?? 0,
otherDependents: data['otherDependents'] as int? ?? 0,
otherIncome: data['otherIncome'] as String? ?? '',
deductions: data['deductions'] as String? ?? '',
extraWithholding: data['extraWithholding'] as String? ?? '',
signature: data['signature'] as String? ?? '',
));
emit(
FormW4State(
firstName: data['firstName'] as String? ?? '',
lastName: data['lastName'] as String? ?? '',
ssn: data['ssn'] as String? ?? '',
address: data['address'] as String? ?? '',
cityStateZip: cityStateZip.contains(',') ? cityStateZip : '',
filingStatus: data['filingStatus'] as String? ?? '',
multipleJobs: data['multipleJobs'] as bool? ?? false,
qualifyingChildren: data['qualifyingChildren'] as int? ?? 0,
otherDependents: data['otherDependents'] as int? ?? 0,
otherIncome: data['otherIncome'] as String? ?? '',
deductions: data['deductions'] as String? ?? '',
extraWithholding: data['extraWithholding'] as String? ?? '',
signature: data['signature'] as String? ?? '',
),
);
}
void nextStep(int totalSteps) {
@@ -62,52 +65,65 @@ class FormW4Cubit extends Cubit<FormW4State> {
void lastNameChanged(String value) => emit(state.copyWith(lastName: value));
void ssnChanged(String value) => emit(state.copyWith(ssn: value));
void addressChanged(String value) => emit(state.copyWith(address: value));
void cityStateZipChanged(String value) => emit(state.copyWith(cityStateZip: value));
void cityStateZipChanged(String value) =>
emit(state.copyWith(cityStateZip: value));
// Form Data
void filingStatusChanged(String value) => emit(state.copyWith(filingStatus: value));
void multipleJobsChanged(bool value) => emit(state.copyWith(multipleJobs: value));
void qualifyingChildrenChanged(int value) => emit(state.copyWith(qualifyingChildren: value));
void otherDependentsChanged(int value) => emit(state.copyWith(otherDependents: value));
void filingStatusChanged(String value) =>
emit(state.copyWith(filingStatus: value));
void multipleJobsChanged(bool value) =>
emit(state.copyWith(multipleJobs: value));
void qualifyingChildrenChanged(int value) =>
emit(state.copyWith(qualifyingChildren: value));
void otherDependentsChanged(int value) =>
emit(state.copyWith(otherDependents: value));
// Adjustments
void otherIncomeChanged(String value) => emit(state.copyWith(otherIncome: value));
void deductionsChanged(String value) => emit(state.copyWith(deductions: value));
void extraWithholdingChanged(String value) => emit(state.copyWith(extraWithholding: value));
void otherIncomeChanged(String value) =>
emit(state.copyWith(otherIncome: value));
void deductionsChanged(String value) =>
emit(state.copyWith(deductions: value));
void extraWithholdingChanged(String value) =>
emit(state.copyWith(extraWithholding: value));
void signatureChanged(String value) => emit(state.copyWith(signature: value));
Future<void> submit() async {
emit(state.copyWith(status: FormW4Status.submitting));
try {
final Map<String, dynamic> formData = {
'firstName': state.firstName,
'lastName': state.lastName,
'ssn': state.ssn,
'address': state.address,
'cityStateZip': state.cityStateZip, // Note: Repository should split this if needed.
'filingStatus': state.filingStatus,
'multipleJobs': state.multipleJobs,
'qualifyingChildren': state.qualifyingChildren,
'otherDependents': state.otherDependents,
'otherIncome': state.otherIncome,
'deductions': state.deductions,
'extraWithholding': state.extraWithholding,
'signature': state.signature,
};
await handleError(
emit: emit,
action: () async {
final Map<String, dynamic> formData = {
'firstName': state.firstName,
'lastName': state.lastName,
'ssn': state.ssn,
'address': state.address,
'cityStateZip':
state.cityStateZip, // Note: Repository should split this if needed.
'filingStatus': state.filingStatus,
'multipleJobs': state.multipleJobs,
'qualifyingChildren': state.qualifyingChildren,
'otherDependents': state.otherDependents,
'otherIncome': state.otherIncome,
'deductions': state.deductions,
'extraWithholding': state.extraWithholding,
'signature': state.signature,
};
final W4TaxForm form = W4TaxForm(
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
title: 'Form W-4',
formData: formData,
);
final W4TaxForm form = W4TaxForm(
id: _formId.isNotEmpty ? _formId : const Uuid().v4(),
title: 'Form W-4',
formData: formData,
);
await _submitW4FormUseCase(form);
emit(state.copyWith(status: FormW4Status.success));
} catch (e) {
emit(state.copyWith(
status: FormW4Status.failure,
errorMessage: e.toString(),
));
}
await _submitW4FormUseCase(form);
emit(state.copyWith(status: FormW4Status.success));
},
onError:
(String errorKey) => state.copyWith(
status: FormW4Status.failure,
errorMessage: errorKey,
),
);
}
}

View File

@@ -147,12 +147,12 @@ class TaxFormsPage extends StatelessWidget {
if (form is I9TaxForm) {
final result = await Modular.to.pushNamed('i9', arguments: form);
if (result == true && context.mounted) {
BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
await BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
}
} else if (form is W4TaxForm) {
final result = await Modular.to.pushNamed('w4', arguments: form);
if (result == true && context.mounted) {
BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
await BlocProvider.of<TaxFormsCubit>(context).loadTaxForms();
}
}
},

View File

@@ -1,35 +1,42 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/add_bank_account_params.dart';
import '../../domain/usecases/add_bank_account_usecase.dart';
import '../../domain/usecases/get_bank_accounts_usecase.dart';
import 'bank_account_state.dart';
class BankAccountCubit extends Cubit<BankAccountState> {
class BankAccountCubit extends Cubit<BankAccountState>
with BlocErrorHandler<BankAccountState> {
final GetBankAccountsUseCase _getBankAccountsUseCase;
final AddBankAccountUseCase _addBankAccountUseCase;
BankAccountCubit({
required GetBankAccountsUseCase getBankAccountsUseCase,
required AddBankAccountUseCase addBankAccountUseCase,
}) : _getBankAccountsUseCase = getBankAccountsUseCase,
_addBankAccountUseCase = addBankAccountUseCase,
super(const BankAccountState());
}) : _getBankAccountsUseCase = getBankAccountsUseCase,
_addBankAccountUseCase = addBankAccountUseCase,
super(const BankAccountState());
Future<void> loadAccounts() async {
emit(state.copyWith(status: BankAccountStatus.loading));
try {
final List<BankAccount> accounts = await _getBankAccountsUseCase();
emit(state.copyWith(
status: BankAccountStatus.loaded,
accounts: accounts,
));
} catch (e) {
emit(state.copyWith(
status: BankAccountStatus.error,
errorMessage: e.toString(),
));
}
await handleError(
emit: emit,
action: () async {
final List<BankAccount> accounts = await _getBankAccountsUseCase();
emit(
state.copyWith(
status: BankAccountStatus.loaded,
accounts: accounts,
),
);
},
onError:
(String errorKey) => state.copyWith(
status: BankAccountStatus.error,
errorMessage: errorKey,
),
);
}
void toggleForm(bool show) {
@@ -43,35 +50,47 @@ class BankAccountCubit extends Cubit<BankAccountState> {
required String type,
}) async {
emit(state.copyWith(status: BankAccountStatus.loading));
// Create domain entity
final BankAccount newAccount = BankAccount(
id: '', // Generated by server usually
userId: '', // Handled by Repo/Auth
bankName: bankName,
accountNumber: accountNumber,
accountName: '',
sortCode: routingNumber,
type: type == 'CHECKING' ? BankAccountType.checking : BankAccountType.savings,
last4: accountNumber.length > 4 ? accountNumber.substring(accountNumber.length - 4) : accountNumber,
isPrimary: false,
id: '', // Generated by server usually
userId: '', // Handled by Repo/Auth
bankName: bankName,
accountNumber: accountNumber,
accountName: '',
sortCode: routingNumber,
type:
type == 'CHECKING'
? BankAccountType.checking
: BankAccountType.savings,
last4:
accountNumber.length > 4
? accountNumber.substring(accountNumber.length - 4)
: accountNumber,
isPrimary: false,
);
try {
await _addBankAccountUseCase(AddBankAccountParams(account: newAccount));
await handleError(
emit: emit,
action: () async {
await _addBankAccountUseCase(AddBankAccountParams(account: newAccount));
// Re-fetch to get latest state including server-generated IDs
await loadAccounts();
emit(state.copyWith(
status: BankAccountStatus.accountAdded,
showForm: false, // Close form on success
));
} catch (e) {
emit(state.copyWith(
status: BankAccountStatus.error,
errorMessage: e.toString(),
));
}
// Re-fetch to get latest state including server-generated IDs
await loadAccounts();
emit(
state.copyWith(
status: BankAccountStatus.accountAdded,
showForm: false, // Close form on success
),
);
},
onError:
(String errorKey) => state.copyWith(
status: BankAccountStatus.error,
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/get_time_cards_arguments.dart';
import '../../domain/usecases/get_time_cards_usecase.dart';
@@ -8,35 +9,55 @@ part 'time_card_event.dart';
part 'time_card_state.dart';
/// BLoC to manage Time Card state.
class TimeCardBloc extends Bloc<TimeCardEvent, TimeCardState> {
class TimeCardBloc extends Bloc<TimeCardEvent, TimeCardState>
with BlocErrorHandler<TimeCardState> {
final GetTimeCardsUseCase getTimeCards;
TimeCardBloc({required this.getTimeCards}) : super(TimeCardInitial()) {
on<LoadTimeCards>(_onLoadTimeCards);
on<ChangeMonth>(_onChangeMonth);
}
/// Handles fetching time cards for the requested month.
Future<void> _onLoadTimeCards(LoadTimeCards event, Emitter<TimeCardState> emit) async {
Future<void> _onLoadTimeCards(
LoadTimeCards event,
Emitter<TimeCardState> emit,
) async {
emit(TimeCardLoading());
try {
final List<TimeCard> cards = await getTimeCards(GetTimeCardsArguments(event.month));
final double totalHours = cards.fold(0.0, (double sum, TimeCard t) => sum + t.totalHours);
final double totalEarnings = cards.fold(0.0, (double sum, TimeCard t) => sum + t.totalPay);
await handleError(
emit: emit,
action: () async {
final List<TimeCard> cards = await getTimeCards(
GetTimeCardsArguments(event.month),
);
emit(TimeCardLoaded(
timeCards: cards,
selectedMonth: event.month,
totalHours: totalHours,
totalEarnings: totalEarnings,
));
} catch (e) {
emit(TimeCardError(e.toString()));
}
final double totalHours = cards.fold(
0.0,
(double sum, TimeCard t) => sum + t.totalHours,
);
final double totalEarnings = cards.fold(
0.0,
(double sum, TimeCard t) => sum + t.totalPay,
);
emit(
TimeCardLoaded(
timeCards: cards,
selectedMonth: event.month,
totalHours: totalHours,
totalEarnings: totalEarnings,
),
);
},
onError: (String errorKey) => TimeCardError(errorKey),
);
}
Future<void> _onChangeMonth(ChangeMonth event, Emitter<TimeCardState> emit) async {
add(LoadTimeCards(event.month));
Future<void> _onChangeMonth(
ChangeMonth event,
Emitter<TimeCardState> emit,
) async {
add(LoadTimeCards(event.month));
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/save_attire_arguments.dart';
@@ -8,7 +9,8 @@ import '../../domain/usecases/save_attire_usecase.dart';
import '../../domain/usecases/upload_attire_photo_usecase.dart';
import 'attire_state.dart';
class AttireCubit extends Cubit<AttireState> {
class AttireCubit extends Cubit<AttireState>
with BlocErrorHandler<AttireState> {
final GetAttireOptionsUseCase _getAttireOptionsUseCase;
final SaveAttireUseCase _saveAttireUseCase;
final UploadAttirePhotoUseCase _uploadAttirePhotoUseCase;
@@ -23,30 +25,41 @@ class AttireCubit extends Cubit<AttireState> {
Future<void> loadOptions() async {
emit(state.copyWith(status: AttireStatus.loading));
try {
final List<AttireItem> options = await _getAttireOptionsUseCase();
// Auto-select mandatory items initially as per prototype
final List<String> mandatoryIds = options
.where((AttireItem e) => e.isMandatory)
.map((AttireItem e) => e.id)
.toList();
final List<String> initialSelection = List<String>.from(state.selectedIds);
for (final String id in mandatoryIds) {
if (!initialSelection.contains(id)) {
initialSelection.add(id);
}
}
await handleError(
emit: emit,
action: () async {
final List<AttireItem> options = await _getAttireOptionsUseCase();
emit(state.copyWith(
status: AttireStatus.success,
options: options,
selectedIds: initialSelection,
));
} catch (e) {
emit(state.copyWith(status: AttireStatus.failure, errorMessage: e.toString()));
}
// Auto-select mandatory items initially as per prototype
final List<String> mandatoryIds =
options
.where((AttireItem e) => e.isMandatory)
.map((AttireItem e) => e.id)
.toList();
final List<String> initialSelection = List<String>.from(
state.selectedIds,
);
for (final String id in mandatoryIds) {
if (!initialSelection.contains(id)) {
initialSelection.add(id);
}
}
emit(
state.copyWith(
status: AttireStatus.success,
options: options,
selectedIds: initialSelection,
),
);
},
onError:
(String errorKey) => state.copyWith(
status: AttireStatus.failure,
errorMessage: errorKey,
),
);
}
void toggleSelection(String id) {
@@ -67,51 +80,81 @@ class AttireCubit extends Cubit<AttireState> {
}
Future<void> uploadPhoto(String itemId) async {
final Map<String, bool> currentUploading = Map<String, bool>.from(state.uploadingStatus);
final Map<String, bool> currentUploading = Map<String, bool>.from(
state.uploadingStatus,
);
currentUploading[itemId] = true;
emit(state.copyWith(uploadingStatus: currentUploading));
try {
final String url = await _uploadAttirePhotoUseCase(
UploadAttirePhotoArguments(itemId: itemId),
);
final Map<String, String> currentPhotos = Map<String, String>.from(state.photoUrls);
currentPhotos[itemId] = url;
// Auto-select item on upload success if not selected
final List<String> currentSelection = List<String>.from(state.selectedIds);
if (!currentSelection.contains(itemId)) {
currentSelection.add(itemId);
}
await handleError(
emit: emit,
action: () async {
final String url = await _uploadAttirePhotoUseCase(
UploadAttirePhotoArguments(itemId: itemId),
);
currentUploading[itemId] = false;
emit(state.copyWith(
uploadingStatus: currentUploading,
photoUrls: currentPhotos,
selectedIds: currentSelection,
));
} catch (e) {
currentUploading[itemId] = false;
emit(state.copyWith(
uploadingStatus: currentUploading,
final Map<String, String> currentPhotos = Map<String, String>.from(
state.photoUrls,
);
currentPhotos[itemId] = url;
// Auto-select item on upload success if not selected
final List<String> currentSelection = List<String>.from(
state.selectedIds,
);
if (!currentSelection.contains(itemId)) {
currentSelection.add(itemId);
}
final Map<String, bool> updatedUploading = Map<String, bool>.from(
state.uploadingStatus,
);
updatedUploading[itemId] = false;
emit(
state.copyWith(
uploadingStatus: updatedUploading,
photoUrls: currentPhotos,
selectedIds: currentSelection,
),
);
},
onError: (String errorKey) {
final Map<String, bool> updatedUploading = Map<String, bool>.from(
state.uploadingStatus,
);
updatedUploading[itemId] = false;
// Could handle error specifically via snackbar event
));
}
// For now, attaching the error message but keeping state generally usable
return state.copyWith(
uploadingStatus: updatedUploading,
errorMessage: errorKey,
);
},
);
}
Future<void> save() async {
if (!state.canSave) return;
emit(state.copyWith(status: AttireStatus.saving));
try {
await _saveAttireUseCase(SaveAttireArguments(
selectedItemIds: state.selectedIds,
photoUrls: state.photoUrls,
));
emit(state.copyWith(status: AttireStatus.saved));
} catch (e) {
emit(state.copyWith(status: AttireStatus.failure, errorMessage: e.toString()));
}
await handleError(
emit: emit,
action: () async {
await _saveAttireUseCase(
SaveAttireArguments(
selectedItemIds: state.selectedIds,
photoUrls: state.photoUrls,
),
);
emit(state.copyWith(status: AttireStatus.saved));
},
onError:
(String errorKey) => state.copyWith(
status: AttireStatus.failure,
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/get_emergency_contacts_arguments.dart';
import '../../domain/arguments/save_emergency_contacts_arguments.dart';
@@ -12,7 +13,8 @@ export 'emergency_contact_state.dart';
// BLoC
class EmergencyContactBloc
extends Bloc<EmergencyContactEvent, EmergencyContactState> {
extends Bloc<EmergencyContactEvent, EmergencyContactState>
with BlocErrorHandler<EmergencyContactState> {
final GetEmergencyContactsUseCase getEmergencyContacts;
final SaveEmergencyContactsUseCase saveEmergencyContacts;
@@ -28,29 +30,30 @@ class EmergencyContactBloc
add(EmergencyContactsLoaded());
}
Future<void> _onLoaded(
EmergencyContactsLoaded event,
Emitter<EmergencyContactState> emit,
) async {
emit(state.copyWith(status: EmergencyContactStatus.loading));
try {
final contacts = await getEmergencyContacts(
const GetEmergencyContactsArguments(),
);
emit(state.copyWith(
status: EmergencyContactStatus.loaded,
contacts: contacts.isNotEmpty
? contacts
: [EmergencyContact.empty()],
));
} catch (e) {
emit(state.copyWith(
await handleError(
emit: emit,
action: () async {
final contacts = await getEmergencyContacts(
const GetEmergencyContactsArguments(),
);
emit(
state.copyWith(
status: EmergencyContactStatus.loaded,
contacts: contacts.isNotEmpty ? contacts : [EmergencyContact.empty()],
),
);
},
onError: (String errorKey) => state.copyWith(
status: EmergencyContactStatus.failure,
errorMessage: e.toString(),
));
}
errorMessage: errorKey,
),
);
}
void _onAdded(
@@ -85,18 +88,19 @@ class EmergencyContactBloc
Emitter<EmergencyContactState> emit,
) async {
emit(state.copyWith(status: EmergencyContactStatus.saving));
try {
await saveEmergencyContacts(
SaveEmergencyContactsArguments(
contacts: state.contacts,
),
);
emit(state.copyWith(status: EmergencyContactStatus.saved));
} catch (e) {
emit(state.copyWith(
await handleError(
emit: emit,
action: () async {
await saveEmergencyContacts(
SaveEmergencyContactsArguments(contacts: state.contacts),
);
emit(state.copyWith(status: EmergencyContactStatus.saved));
},
onError: (String errorKey) => state.copyWith(
status: EmergencyContactStatus.failure,
errorMessage: e.toString(),
));
}
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/save_experience_arguments.dart';
import '../../domain/usecases/get_staff_industries_usecase.dart';
@@ -92,8 +93,8 @@ class ExperienceState extends Equatable {
}
// BLoC
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
with BlocErrorHandler<ExperienceState> {
final GetStaffIndustriesUseCase getIndustries;
final GetStaffSkillsUseCase getSkills;
final SaveExperienceUseCase saveExperience;
@@ -102,10 +103,12 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
required this.getIndustries,
required this.getSkills,
required this.saveExperience,
}) : super(const ExperienceState(
availableIndustries: Industry.values,
availableSkills: ExperienceSkill.values,
)) {
}) : super(
const ExperienceState(
availableIndustries: Industry.values,
availableSkills: ExperienceSkill.values,
),
) {
on<ExperienceLoaded>(_onLoaded);
on<ExperienceIndustryToggled>(_onIndustryToggled);
on<ExperienceSkillToggled>(_onSkillToggled);
@@ -120,26 +123,28 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
Emitter<ExperienceState> emit,
) async {
emit(state.copyWith(status: ExperienceStatus.loading));
try {
final results = await Future.wait([
getIndustries(),
getSkills(),
]);
await handleError(
emit: emit,
action: () async {
final results = await Future.wait([getIndustries(), getSkills()]);
emit(state.copyWith(
status: ExperienceStatus.initial,
selectedIndustries: results[0]
.map((e) => Industry.fromString(e))
.whereType<Industry>()
.toList(),
selectedSkills: results[1],
));
} catch (e) {
emit(state.copyWith(
emit(
state.copyWith(
status: ExperienceStatus.initial,
selectedIndustries:
results[0]
.map((e) => Industry.fromString(e))
.whereType<Industry>()
.toList(),
selectedSkills: results[1],
),
);
},
onError: (String errorKey) => state.copyWith(
status: ExperienceStatus.failure,
errorMessage: e.toString(),
));
}
errorMessage: errorKey,
),
);
}
void _onIndustryToggled(
@@ -183,19 +188,22 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
Emitter<ExperienceState> emit,
) async {
emit(state.copyWith(status: ExperienceStatus.loading));
try {
await saveExperience(
SaveExperienceArguments(
industries: state.selectedIndustries.map((e) => e.value).toList(),
skills: state.selectedSkills,
),
);
emit(state.copyWith(status: ExperienceStatus.success));
} catch (e) {
emit(state.copyWith(
await handleError(
emit: emit,
action: () async {
await saveExperience(
SaveExperienceArguments(
industries: state.selectedIndustries.map((e) => e.value).toList(),
skills: state.selectedSkills,
),
);
emit(state.copyWith(status: ExperienceStatus.success));
},
onError: (String errorKey) => state.copyWith(
status: ExperienceStatus.failure,
errorMessage: e.toString(),
));
}
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/usecases/get_personal_info_usecase.dart';
@@ -13,17 +14,17 @@ import 'personal_info_state.dart';
/// during onboarding or profile editing. It delegates business logic to
/// use cases following Clean Architecture principles.
class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
with BlocErrorHandler<PersonalInfoState>
implements Disposable {
/// Creates a [PersonalInfoBloc].
///
/// Requires the use cases to load and update the profile.
PersonalInfoBloc({
required GetPersonalInfoUseCase getPersonalInfoUseCase,
required UpdatePersonalInfoUseCase updatePersonalInfoUseCase,
}) : _getPersonalInfoUseCase = getPersonalInfoUseCase,
_updatePersonalInfoUseCase = updatePersonalInfoUseCase,
super(const PersonalInfoState.initial()) {
}) : _getPersonalInfoUseCase = getPersonalInfoUseCase,
_updatePersonalInfoUseCase = updatePersonalInfoUseCase,
super(const PersonalInfoState.initial()) {
on<PersonalInfoLoadRequested>(_onLoadRequested);
on<PersonalInfoFieldChanged>(_onFieldChanged);
on<PersonalInfoAddressSelected>(_onAddressSelected);
@@ -40,32 +41,37 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
Emitter<PersonalInfoState> emit,
) async {
emit(state.copyWith(status: PersonalInfoStatus.loading));
try {
final Staff staff = await _getPersonalInfoUseCase();
// Initialize form values from staff entity
// Note: Staff entity currently stores address as a string, but we want to map it to 'preferredLocations'
final Map<String, dynamic> initialValues = <String, dynamic>{
'name': staff.name,
'email': staff.email,
'phone': staff.phone,
'preferredLocations': staff.address != null
? <String?>[staff.address]
: <dynamic>[], // TODO: Map correctly when Staff entity supports list
'avatar': staff.avatar,
};
await handleError(
emit: emit,
action: () async {
final Staff staff = await _getPersonalInfoUseCase();
emit(state.copyWith(
status: PersonalInfoStatus.loaded,
staff: staff,
formValues: initialValues,
));
} catch (e) {
emit(state.copyWith(
// Initialize form values from staff entity
// Note: Staff entity currently stores address as a string, but we want to map it to 'preferredLocations'
final Map<String, dynamic> initialValues = <String, dynamic>{
'name': staff.name,
'email': staff.email,
'phone': staff.phone,
'preferredLocations':
staff.address != null
? <String?>[staff.address]
: <dynamic>[], // TODO: Map correctly when Staff entity supports list
'avatar': staff.avatar,
};
emit(
state.copyWith(
status: PersonalInfoStatus.loaded,
staff: staff,
formValues: initialValues,
),
);
},
onError: (String errorKey) => state.copyWith(
status: PersonalInfoStatus.error,
errorMessage: e.toString(),
));
}
errorMessage: errorKey,
),
);
}
/// Handles updating a field value in the current staff profile.
@@ -86,43 +92,48 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
if (state.staff == null) return;
emit(state.copyWith(status: PersonalInfoStatus.saving));
try {
final Staff updatedStaff = await _updatePersonalInfoUseCase(
UpdatePersonalInfoParams(
staffId: state.staff!.id,
data: state.formValues,
),
);
// Update local state with the returned staff and keep form values in sync
final Map<String, dynamic> newValues = <String, dynamic>{
'name': updatedStaff.name,
'email': updatedStaff.email,
'phone': updatedStaff.phone,
'preferredLocations': updatedStaff.address != null
? <String?>[updatedStaff.address]
: <dynamic>[],
'avatar': updatedStaff.avatar,
};
await handleError(
emit: emit,
action: () async {
final Staff updatedStaff = await _updatePersonalInfoUseCase(
UpdatePersonalInfoParams(
staffId: state.staff!.id,
data: state.formValues,
),
);
emit(state.copyWith(
status: PersonalInfoStatus.saved,
staff: updatedStaff,
formValues: newValues,
));
} catch (e) {
emit(state.copyWith(
// Update local state with the returned staff and keep form values in sync
final Map<String, dynamic> newValues = <String, dynamic>{
'name': updatedStaff.name,
'email': updatedStaff.email,
'phone': updatedStaff.phone,
'preferredLocations':
updatedStaff.address != null
? <String?>[updatedStaff.address]
: <dynamic>[],
'avatar': updatedStaff.avatar,
};
emit(
state.copyWith(
status: PersonalInfoStatus.saved,
staff: updatedStaff,
formValues: newValues,
),
);
},
onError: (String errorKey) => state.copyWith(
status: PersonalInfoStatus.error,
errorMessage: e.toString(),
));
}
errorMessage: errorKey,
),
);
}
void _onAddressSelected(
PersonalInfoAddressSelected event,
Emitter<PersonalInfoState> emit,
) {
// TODO: Implement Google Places logic if needed
// TODO: Implement Google Places logic if needed
}
/// With _onPhotoUploadRequested and _onSaveRequested removed or renamed,
@@ -133,3 +144,4 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
close();
}
}