feat: Refactor context reading in emergency contact and FAQs widgets

- Updated the context reading method in `EmergencyContactAddButton` and `EmergencyContactFormItem` to use `ReadContext`.
- Modified the `FaqsWidget` to utilize `ReadContext` for fetching FAQs.
- Adjusted the `PrivacySectionWidget` to read from `PrivacySecurityBloc` using `ReadContext`.

feat: Implement Firebase Auth isolation pattern

- Introduced `FirebaseAuthService` and `FirebaseAuthServiceImpl` to abstract Firebase Auth operations.
- Ensured features do not directly import `firebase_auth`, adhering to architecture rules.

feat: Create repository interfaces for billing and coverage

- Added `BillingRepositoryInterface` for billing-related operations.
- Created `CoverageRepositoryInterface` for coverage data access.

feat: Add use cases for order management

- Implemented use cases for fetching hubs, managers, and roles related to orders.
- Created `GetHubsUseCase`, `GetManagersByHubUseCase`, and `GetRolesByVendorUseCase`.

feat: Develop report use cases for client reports

- Added use cases for fetching various reports including coverage, daily operations, forecast, no-show, performance, and spend reports.
- Implemented `GetCoverageReportUseCase`, `GetDailyOpsReportUseCase`, `GetForecastReportUseCase`, `GetNoShowReportUseCase`, `GetPerformanceReportUseCase`, and `GetSpendReportUseCase`.

feat: Establish profile repository and use cases

- Created `ProfileRepositoryInterface` for staff profile data access.
- Implemented use cases for retrieving staff profile and section statuses: `GetStaffProfileUseCase` and `GetProfileSectionsUseCase`.
- Added `SignOutUseCase` for signing out the current user.
This commit is contained in:
Achintha Isuru
2026-03-19 01:10:27 -04:00
parent a45a3f6af1
commit 843eec5692
123 changed files with 2102 additions and 1087 deletions

View File

@@ -3,12 +3,12 @@ import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// V2 API implementation of [ReportsRepository].
/// V2 API implementation of [ReportsRepositoryInterface].
///
/// Each method hits its corresponding `ClientEndpoints.reports*` endpoint,
/// passing date-range query parameters, and deserialises the JSON response
/// into the relevant domain entity.
class ReportsRepositoryImpl implements ReportsRepository {
class ReportsRepositoryImpl implements ReportsRepositoryInterface {
/// Creates a [ReportsRepositoryImpl].
ReportsRepositoryImpl({required BaseApiService apiService})
: _apiService = apiService;

View File

@@ -0,0 +1,13 @@
import 'package:krow_core/core.dart';
/// Arguments for the daily operations report use case.
class DailyOpsArguments extends UseCaseArgument {
/// Creates [DailyOpsArguments].
const DailyOpsArguments({required this.date});
/// The date to fetch the daily operations report for.
final DateTime date;
@override
List<Object?> get props => <Object?>[date];
}

View File

@@ -0,0 +1,19 @@
import 'package:krow_core/core.dart';
/// Arguments for use cases that require a date range (start and end dates).
class DateRangeArguments extends UseCaseArgument {
/// Creates [DateRangeArguments].
const DateRangeArguments({
required this.startDate,
required this.endDate,
});
/// Start of the reporting period.
final DateTime startDate;
/// End of the reporting period.
final DateTime endDate;
@override
List<Object?> get props => <Object?>[startDate, endDate];
}

View File

@@ -1,7 +1,7 @@
import 'package:krow_domain/krow_domain.dart';
/// Contract for fetching report data from the V2 API.
abstract class ReportsRepository {
abstract class ReportsRepositoryInterface {
/// Fetches the daily operations report for a given [date].
Future<DailyOpsReport> getDailyOpsReport({
required DateTime date,

View File

@@ -0,0 +1,23 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// Fetches the coverage report for a date range.
class GetCoverageReportUseCase
implements UseCase<DateRangeArguments, CoverageReport> {
/// Creates a [GetCoverageReportUseCase].
GetCoverageReportUseCase(this._repository);
/// The repository providing report data.
final ReportsRepositoryInterface _repository;
@override
Future<CoverageReport> call(DateRangeArguments input) {
return _repository.getCoverageReport(
startDate: input.startDate,
endDate: input.endDate,
);
}
}

View File

@@ -0,0 +1,20 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/arguments/daily_ops_arguments.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// Fetches the daily operations report for a single date.
class GetDailyOpsReportUseCase
implements UseCase<DailyOpsArguments, DailyOpsReport> {
/// Creates a [GetDailyOpsReportUseCase].
GetDailyOpsReportUseCase(this._repository);
/// The repository providing report data.
final ReportsRepositoryInterface _repository;
@override
Future<DailyOpsReport> call(DailyOpsArguments input) {
return _repository.getDailyOpsReport(date: input.date);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// Fetches the forecast report for a date range.
class GetForecastReportUseCase
implements UseCase<DateRangeArguments, ForecastReport> {
/// Creates a [GetForecastReportUseCase].
GetForecastReportUseCase(this._repository);
/// The repository providing report data.
final ReportsRepositoryInterface _repository;
@override
Future<ForecastReport> call(DateRangeArguments input) {
return _repository.getForecastReport(
startDate: input.startDate,
endDate: input.endDate,
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// Fetches the no-show report for a date range.
class GetNoShowReportUseCase
implements UseCase<DateRangeArguments, NoShowReport> {
/// Creates a [GetNoShowReportUseCase].
GetNoShowReportUseCase(this._repository);
/// The repository providing report data.
final ReportsRepositoryInterface _repository;
@override
Future<NoShowReport> call(DateRangeArguments input) {
return _repository.getNoShowReport(
startDate: input.startDate,
endDate: input.endDate,
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// Fetches the performance report for a date range.
class GetPerformanceReportUseCase
implements UseCase<DateRangeArguments, PerformanceReport> {
/// Creates a [GetPerformanceReportUseCase].
GetPerformanceReportUseCase(this._repository);
/// The repository providing report data.
final ReportsRepositoryInterface _repository;
@override
Future<PerformanceReport> call(DateRangeArguments input) {
return _repository.getPerformanceReport(
startDate: input.startDate,
endDate: input.endDate,
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// Fetches the high-level report summary for a date range.
class GetReportsSummaryUseCase
implements UseCase<DateRangeArguments, ReportSummary> {
/// Creates a [GetReportsSummaryUseCase].
GetReportsSummaryUseCase(this._repository);
/// The repository providing report data.
final ReportsRepositoryInterface _repository;
@override
Future<ReportSummary> call(DateRangeArguments input) {
return _repository.getReportsSummary(
startDate: input.startDate,
endDate: input.endDate,
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
/// Fetches the spend report for a date range.
class GetSpendReportUseCase
implements UseCase<DateRangeArguments, SpendReport> {
/// Creates a [GetSpendReportUseCase].
GetSpendReportUseCase(this._repository);
/// The repository providing report data.
final ReportsRepositoryInterface _repository;
@override
Future<SpendReport> call(DateRangeArguments input) {
return _repository.getSpendReport(
startDate: input.startDate,
endDate: input.endDate,
);
}
}

View File

@@ -1,22 +1,23 @@
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/usecases/get_coverage_report_usecase.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_event.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// BLoC that loads the [CoverageReport].
/// BLoC that loads the [CoverageReport] via [GetCoverageReportUseCase].
class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
with BlocErrorHandler<CoverageState> {
/// Creates a [CoverageBloc].
CoverageBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository,
CoverageBloc({required GetCoverageReportUseCase getCoverageReportUseCase})
: _getCoverageReportUseCase = getCoverageReportUseCase,
super(CoverageInitial()) {
on<LoadCoverageReport>(_onLoadCoverageReport);
}
/// The repository used to fetch report data.
final ReportsRepository _reportsRepository;
/// The use case for fetching the coverage report.
final GetCoverageReportUseCase _getCoverageReportUseCase;
Future<void> _onLoadCoverageReport(
LoadCoverageReport event,
@@ -26,10 +27,11 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
emit: emit,
action: () async {
emit(CoverageLoading());
final CoverageReport report =
await _reportsRepository.getCoverageReport(
startDate: event.startDate,
endDate: event.endDate,
final CoverageReport report = await _getCoverageReportUseCase.call(
DateRangeArguments(
startDate: event.startDate,
endDate: event.endDate,
),
);
emit(CoverageLoaded(report));
},

View File

@@ -1,30 +1,39 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:equatable/equatable.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
/// Base state for the coverage report BLoC.
abstract class CoverageState extends Equatable {
/// Creates a [CoverageState].
const CoverageState();
@override
List<Object?> get props => <Object?>[];
}
/// Initial state before any coverage report has been requested.
class CoverageInitial extends CoverageState {}
/// State while the coverage report is loading.
class CoverageLoading extends CoverageState {}
/// State when the coverage report has loaded successfully.
class CoverageLoaded extends CoverageState {
/// Creates a [CoverageLoaded] with the given [report].
const CoverageLoaded(this.report);
/// The loaded coverage report data.
final CoverageReport report;
@override
List<Object?> get props => <Object?>[report];
}
/// State when loading the coverage report has failed.
class CoverageError extends CoverageState {
/// Creates a [CoverageError] with the given error [message].
const CoverageError(this.message);
/// The error message describing the failure.
final String message;
@override

View File

@@ -1,22 +1,23 @@
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/arguments/daily_ops_arguments.dart';
import 'package:client_reports/src/domain/usecases/get_daily_ops_report_usecase.dart';
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_event.dart';
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// BLoC that loads the [DailyOpsReport].
/// BLoC that loads the [DailyOpsReport] via [GetDailyOpsReportUseCase].
class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState>
with BlocErrorHandler<DailyOpsState> {
/// Creates a [DailyOpsBloc].
DailyOpsBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository,
DailyOpsBloc({required GetDailyOpsReportUseCase getDailyOpsReportUseCase})
: _getDailyOpsReportUseCase = getDailyOpsReportUseCase,
super(DailyOpsInitial()) {
on<LoadDailyOpsReport>(_onLoadDailyOpsReport);
}
/// The repository used to fetch report data.
final ReportsRepository _reportsRepository;
/// The use case for fetching the daily operations report.
final GetDailyOpsReportUseCase _getDailyOpsReportUseCase;
Future<void> _onLoadDailyOpsReport(
LoadDailyOpsReport event,
@@ -26,9 +27,8 @@ class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState>
emit: emit,
action: () async {
emit(DailyOpsLoading());
final DailyOpsReport report =
await _reportsRepository.getDailyOpsReport(
date: event.date,
final DailyOpsReport report = await _getDailyOpsReportUseCase.call(
DailyOpsArguments(date: event.date),
);
emit(DailyOpsLoaded(report));
},

View File

@@ -1,30 +1,39 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:equatable/equatable.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
/// Base state for the daily operations report BLoC.
abstract class DailyOpsState extends Equatable {
/// Creates a [DailyOpsState].
const DailyOpsState();
@override
List<Object?> get props => <Object?>[];
}
/// Initial state before any report has been requested.
class DailyOpsInitial extends DailyOpsState {}
/// State while the daily operations report is loading.
class DailyOpsLoading extends DailyOpsState {}
/// State when the daily operations report has loaded successfully.
class DailyOpsLoaded extends DailyOpsState {
/// Creates a [DailyOpsLoaded] with the given [report].
const DailyOpsLoaded(this.report);
/// The loaded daily operations report data.
final DailyOpsReport report;
@override
List<Object?> get props => <Object?>[report];
}
/// State when loading the daily operations report has failed.
class DailyOpsError extends DailyOpsState {
/// Creates a [DailyOpsError] with the given error [message].
const DailyOpsError(this.message);
/// The error message describing the failure.
final String message;
@override

View File

@@ -1,22 +1,23 @@
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/usecases/get_forecast_report_usecase.dart';
import 'package:client_reports/src/presentation/blocs/forecast/forecast_event.dart';
import 'package:client_reports/src/presentation/blocs/forecast/forecast_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// BLoC that loads the [ForecastReport].
/// BLoC that loads the [ForecastReport] via [GetForecastReportUseCase].
class ForecastBloc extends Bloc<ForecastEvent, ForecastState>
with BlocErrorHandler<ForecastState> {
/// Creates a [ForecastBloc].
ForecastBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository,
ForecastBloc({required GetForecastReportUseCase getForecastReportUseCase})
: _getForecastReportUseCase = getForecastReportUseCase,
super(ForecastInitial()) {
on<LoadForecastReport>(_onLoadForecastReport);
}
/// The repository used to fetch report data.
final ReportsRepository _reportsRepository;
/// The use case for fetching the forecast report.
final GetForecastReportUseCase _getForecastReportUseCase;
Future<void> _onLoadForecastReport(
LoadForecastReport event,
@@ -26,10 +27,11 @@ class ForecastBloc extends Bloc<ForecastEvent, ForecastState>
emit: emit,
action: () async {
emit(ForecastLoading());
final ForecastReport report =
await _reportsRepository.getForecastReport(
startDate: event.startDate,
endDate: event.endDate,
final ForecastReport report = await _getForecastReportUseCase.call(
DateRangeArguments(
startDate: event.startDate,
endDate: event.endDate,
),
);
emit(ForecastLoaded(report));
},

View File

@@ -1,30 +1,39 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:equatable/equatable.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
/// Base state for the forecast report BLoC.
abstract class ForecastState extends Equatable {
/// Creates a [ForecastState].
const ForecastState();
@override
List<Object?> get props => <Object?>[];
}
/// Initial state before any forecast has been requested.
class ForecastInitial extends ForecastState {}
/// State while the forecast report is loading.
class ForecastLoading extends ForecastState {}
/// State when the forecast report has loaded successfully.
class ForecastLoaded extends ForecastState {
/// Creates a [ForecastLoaded] with the given [report].
const ForecastLoaded(this.report);
/// The loaded forecast report data.
final ForecastReport report;
@override
List<Object?> get props => <Object?>[report];
}
/// State when loading the forecast report has failed.
class ForecastError extends ForecastState {
/// Creates a [ForecastError] with the given error [message].
const ForecastError(this.message);
/// The error message describing the failure.
final String message;
@override

View File

@@ -1,22 +1,23 @@
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/usecases/get_no_show_report_usecase.dart';
import 'package:client_reports/src/presentation/blocs/no_show/no_show_event.dart';
import 'package:client_reports/src/presentation/blocs/no_show/no_show_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// BLoC that loads the [NoShowReport].
/// BLoC that loads the [NoShowReport] via [GetNoShowReportUseCase].
class NoShowBloc extends Bloc<NoShowEvent, NoShowState>
with BlocErrorHandler<NoShowState> {
/// Creates a [NoShowBloc].
NoShowBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository,
NoShowBloc({required GetNoShowReportUseCase getNoShowReportUseCase})
: _getNoShowReportUseCase = getNoShowReportUseCase,
super(NoShowInitial()) {
on<LoadNoShowReport>(_onLoadNoShowReport);
}
/// The repository used to fetch report data.
final ReportsRepository _reportsRepository;
/// The use case for fetching the no-show report.
final GetNoShowReportUseCase _getNoShowReportUseCase;
Future<void> _onLoadNoShowReport(
LoadNoShowReport event,
@@ -26,9 +27,11 @@ class NoShowBloc extends Bloc<NoShowEvent, NoShowState>
emit: emit,
action: () async {
emit(NoShowLoading());
final NoShowReport report = await _reportsRepository.getNoShowReport(
startDate: event.startDate,
endDate: event.endDate,
final NoShowReport report = await _getNoShowReportUseCase.call(
DateRangeArguments(
startDate: event.startDate,
endDate: event.endDate,
),
);
emit(NoShowLoaded(report));
},

View File

@@ -1,30 +1,39 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:equatable/equatable.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
/// Base state for the no-show report BLoC.
abstract class NoShowState extends Equatable {
/// Creates a [NoShowState].
const NoShowState();
@override
List<Object?> get props => <Object?>[];
}
/// Initial state before any no-show report has been requested.
class NoShowInitial extends NoShowState {}
/// State while the no-show report is loading.
class NoShowLoading extends NoShowState {}
/// State when the no-show report has loaded successfully.
class NoShowLoaded extends NoShowState {
/// Creates a [NoShowLoaded] with the given [report].
const NoShowLoaded(this.report);
/// The loaded no-show report data.
final NoShowReport report;
@override
List<Object?> get props => <Object?>[report];
}
/// State when loading the no-show report has failed.
class NoShowError extends NoShowState {
/// Creates a [NoShowError] with the given error [message].
const NoShowError(this.message);
/// The error message describing the failure.
final String message;
@override

View File

@@ -1,22 +1,24 @@
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/usecases/get_performance_report_usecase.dart';
import 'package:client_reports/src/presentation/blocs/performance/performance_event.dart';
import 'package:client_reports/src/presentation/blocs/performance/performance_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// BLoC that loads the [PerformanceReport].
/// BLoC that loads the [PerformanceReport] via [GetPerformanceReportUseCase].
class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState>
with BlocErrorHandler<PerformanceState> {
/// Creates a [PerformanceBloc].
PerformanceBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository,
PerformanceBloc({
required GetPerformanceReportUseCase getPerformanceReportUseCase,
}) : _getPerformanceReportUseCase = getPerformanceReportUseCase,
super(PerformanceInitial()) {
on<LoadPerformanceReport>(_onLoadPerformanceReport);
}
/// The repository used to fetch report data.
final ReportsRepository _reportsRepository;
/// The use case for fetching the performance report.
final GetPerformanceReportUseCase _getPerformanceReportUseCase;
Future<void> _onLoadPerformanceReport(
LoadPerformanceReport event,
@@ -26,10 +28,11 @@ class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState>
emit: emit,
action: () async {
emit(PerformanceLoading());
final PerformanceReport report =
await _reportsRepository.getPerformanceReport(
startDate: event.startDate,
endDate: event.endDate,
final PerformanceReport report = await _getPerformanceReportUseCase.call(
DateRangeArguments(
startDate: event.startDate,
endDate: event.endDate,
),
);
emit(PerformanceLoaded(report));
},

View File

@@ -1,30 +1,39 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:equatable/equatable.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
/// Base state for the performance report BLoC.
abstract class PerformanceState extends Equatable {
/// Creates a [PerformanceState].
const PerformanceState();
@override
List<Object?> get props => <Object?>[];
}
/// Initial state before any performance report has been requested.
class PerformanceInitial extends PerformanceState {}
/// State while the performance report is loading.
class PerformanceLoading extends PerformanceState {}
/// State when the performance report has loaded successfully.
class PerformanceLoaded extends PerformanceState {
/// Creates a [PerformanceLoaded] with the given [report].
const PerformanceLoaded(this.report);
/// The loaded performance report data.
final PerformanceReport report;
@override
List<Object?> get props => <Object?>[report];
}
/// State when loading the performance report has failed.
class PerformanceError extends PerformanceState {
/// Creates a [PerformanceError] with the given error [message].
const PerformanceError(this.message);
/// The error message describing the failure.
final String message;
@override

View File

@@ -1,22 +1,23 @@
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/usecases/get_spend_report_usecase.dart';
import 'package:client_reports/src/presentation/blocs/spend/spend_event.dart';
import 'package:client_reports/src/presentation/blocs/spend/spend_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// BLoC that loads the [SpendReport].
/// BLoC that loads the [SpendReport] via [GetSpendReportUseCase].
class SpendBloc extends Bloc<SpendEvent, SpendState>
with BlocErrorHandler<SpendState> {
/// Creates a [SpendBloc].
SpendBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository,
SpendBloc({required GetSpendReportUseCase getSpendReportUseCase})
: _getSpendReportUseCase = getSpendReportUseCase,
super(SpendInitial()) {
on<LoadSpendReport>(_onLoadSpendReport);
}
/// The repository used to fetch report data.
final ReportsRepository _reportsRepository;
/// The use case for fetching the spend report.
final GetSpendReportUseCase _getSpendReportUseCase;
Future<void> _onLoadSpendReport(
LoadSpendReport event,
@@ -26,9 +27,11 @@ class SpendBloc extends Bloc<SpendEvent, SpendState>
emit: emit,
action: () async {
emit(SpendLoading());
final SpendReport report = await _reportsRepository.getSpendReport(
startDate: event.startDate,
endDate: event.endDate,
final SpendReport report = await _getSpendReportUseCase.call(
DateRangeArguments(
startDate: event.startDate,
endDate: event.endDate,
),
);
emit(SpendLoaded(report));
},

View File

@@ -1,30 +1,39 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:equatable/equatable.dart';
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
/// Base state for the spend report BLoC.
abstract class SpendState extends Equatable {
/// Creates a [SpendState].
const SpendState();
@override
List<Object?> get props => <Object?>[];
}
/// Initial state before any spend report has been requested.
class SpendInitial extends SpendState {}
/// State while the spend report is loading.
class SpendLoading extends SpendState {}
/// State when the spend report has loaded successfully.
class SpendLoaded extends SpendState {
/// Creates a [SpendLoaded] with the given [report].
const SpendLoaded(this.report);
/// The loaded spend report data.
final SpendReport report;
@override
List<Object?> get props => <Object?>[report];
}
/// State when loading the spend report has failed.
class SpendError extends SpendState {
/// Creates a [SpendError] with the given error [message].
const SpendError(this.message);
/// The error message describing the failure.
final String message;
@override

View File

@@ -1,23 +1,25 @@
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/arguments/date_range_arguments.dart';
import 'package:client_reports/src/domain/usecases/get_reports_summary_usecase.dart';
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_event.dart';
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// BLoC that loads the high-level [ReportSummary] for the reports dashboard.
/// BLoC that loads the high-level [ReportSummary] via [GetReportsSummaryUseCase].
class ReportsSummaryBloc
extends Bloc<ReportsSummaryEvent, ReportsSummaryState>
with BlocErrorHandler<ReportsSummaryState> {
/// Creates a [ReportsSummaryBloc].
ReportsSummaryBloc({required ReportsRepository reportsRepository})
: _reportsRepository = reportsRepository,
ReportsSummaryBloc({
required GetReportsSummaryUseCase getReportsSummaryUseCase,
}) : _getReportsSummaryUseCase = getReportsSummaryUseCase,
super(ReportsSummaryInitial()) {
on<LoadReportsSummary>(_onLoadReportsSummary);
}
/// The repository used to fetch summary data.
final ReportsRepository _reportsRepository;
/// The use case for fetching the report summary.
final GetReportsSummaryUseCase _getReportsSummaryUseCase;
Future<void> _onLoadReportsSummary(
LoadReportsSummary event,
@@ -27,10 +29,11 @@ class ReportsSummaryBloc
emit: emit,
action: () async {
emit(ReportsSummaryLoading());
final ReportSummary summary =
await _reportsRepository.getReportsSummary(
startDate: event.startDate,
endDate: event.endDate,
final ReportSummary summary = await _getReportsSummaryUseCase.call(
DateRangeArguments(
startDate: event.startDate,
endDate: event.endDate,
),
);
emit(ReportsSummaryLoaded(summary));
},

View File

@@ -1,6 +1,7 @@
import 'package:client_reports/src/presentation/blocs/coverage/coverage_bloc.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_bloc.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_event.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_state.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
@@ -10,9 +11,9 @@ import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
/// Page displaying the coverage report with summary and daily breakdown.
class CoverageReportPage extends StatefulWidget {
/// Creates a [CoverageReportPage].
const CoverageReportPage({super.key});
@override
@@ -86,17 +87,14 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
children: <Widget>[
Text(
context.t.client_reports.coverage_report.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
style: UiTypography.title1b.copyWith(
color: UiColors.white,
),
),
Text(
context.t.client_reports.coverage_report
.subtitle,
style: TextStyle(
fontSize: 12,
style: UiTypography.body3r.copyWith(
color: UiColors.white.withOpacity(0.7),
),
),
@@ -143,9 +141,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
// Daily List
Text(
context.t.client_reports.coverage_report.next_7_days,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
style: UiTypography.body3b.copyWith(
color: UiColors.textSecondary,
letterSpacing: 1.2,
),
@@ -177,17 +173,25 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
}
}
/// Summary card for coverage metrics with icon and value.
class _CoverageSummaryCard extends StatelessWidget {
const _CoverageSummaryCard({
required this.label,
required this.value,
required this.icon,
required this.color,
});
/// The metric label text.
final String label;
/// The metric value text.
final String value;
/// The icon to display.
final IconData icon;
/// The icon and accent color.
final Color color;
@override
@@ -216,26 +220,42 @@ class _CoverageSummaryCard extends StatelessWidget {
child: Icon(icon, size: 16, color: color),
),
const SizedBox(height: 12),
Text(label, style: const TextStyle(fontSize: 12, color: UiColors.textSecondary)),
Text(
label,
style: UiTypography.body3r.copyWith(
color: UiColors.textSecondary,
),
),
const SizedBox(height: 4),
Text(value, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
Text(
value,
style: UiTypography.headline3b,
),
],
),
);
}
}
/// List item showing daily coverage with progress bar.
class _CoverageListItem extends StatelessWidget {
const _CoverageListItem({
required this.date,
required this.needed,
required this.filled,
required this.percentage,
});
/// The formatted date string.
final String date;
/// The number of workers needed.
final int needed;
/// The number of workers filled.
final int filled;
/// The coverage percentage.
final double percentage;
@override
@@ -262,7 +282,10 @@ class _CoverageListItem extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(date, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(
date,
style: UiTypography.body2b,
),
const SizedBox(height: 4),
// Progress Bar
ClipRRect(
@@ -283,13 +306,11 @@ class _CoverageListItem extends StatelessWidget {
children: <Widget>[
Text(
'$filled/$needed',
style: const TextStyle(fontWeight: FontWeight.bold),
style: UiTypography.body2b,
),
Text(
'${percentage.toStringAsFixed(0)}%',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
style: UiTypography.body3b.copyWith(
color: statusColor,
),
),
@@ -300,4 +321,3 @@ class _CoverageListItem extends StatelessWidget {
);
}
}

View File

@@ -1,6 +1,7 @@
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_bloc.dart';
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_bloc.dart';
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_event.dart';
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_state.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
@@ -10,9 +11,9 @@ import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
/// Page displaying the daily operations report with shift stats and list.
class DailyOpsReportPage extends StatefulWidget {
/// Creates a [DailyOpsReportPage].
const DailyOpsReportPage({super.key});
@override
@@ -117,17 +118,14 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
Text(
context.t.client_reports.daily_ops_report
.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
style: UiTypography.title1b.copyWith(
color: UiColors.white,
),
),
Text(
context.t.client_reports.daily_ops_report
.subtitle,
style: TextStyle(
fontSize: 12,
style: UiTypography.body3r.copyWith(
color: UiColors.white.withOpacity(0.7),
),
),
@@ -135,52 +133,6 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
),
],
),
/*
GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
context.t.client_reports.daily_ops_report
.placeholders.export_message,
),
duration: const Duration(seconds: 2),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(
UiIcons.download,
size: 14,
color: UiColors.primary,
),
const SizedBox(width: 6),
Text(
context.t.client_reports.quick_reports
.export_all
.split(' ')
.first,
style: const TextStyle(
color: UiColors.primary,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
*/
],
),
),
@@ -223,10 +175,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
Text(
DateFormat('MMM dd, yyyy')
.format(_selectedDate),
style: const TextStyle(
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.body2b,
),
],
),
@@ -325,10 +274,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
context.t.client_reports.daily_ops_report
.all_shifts_title
.toUpperCase(),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
style: UiTypography.body2b.copyWith(
letterSpacing: 0.5,
),
),
@@ -377,8 +323,8 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
}
}
/// Stat card showing a metric with icon, value, and colored badge.
class _OpsStatCard extends StatelessWidget {
const _OpsStatCard({
required this.label,
required this.value,
@@ -386,10 +332,20 @@ class _OpsStatCard extends StatelessWidget {
required this.color,
required this.icon,
});
/// The metric label text.
final String label;
/// The metric value text.
final String value;
/// The badge sub-value text.
final String subValue;
/// The theme color for icon and badge.
final Color color;
/// The icon to display.
final IconData icon;
@override
@@ -412,10 +368,8 @@ class _OpsStatCard extends StatelessWidget {
Expanded(
child: Text(
label,
style: const TextStyle(
fontSize: 12,
style: UiTypography.body3m.copyWith(
color: UiColors.textSecondary,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -428,15 +382,8 @@ class _OpsStatCard extends StatelessWidget {
children: <Widget>[
Text(
value,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.display1b,
),
//UiChip(label: subValue),
// Colored pill badge (matches prototype)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
@@ -448,9 +395,7 @@ class _OpsStatCard extends StatelessWidget {
),
child: Text(
subValue,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
style: UiTypography.body3b.copyWith(
color: color,
),
),
@@ -463,8 +408,8 @@ class _OpsStatCard extends StatelessWidget {
}
}
/// A single shift row in the daily operations list.
class _ShiftListItem extends StatelessWidget {
const _ShiftListItem({
required this.title,
required this.location,
@@ -474,12 +419,26 @@ class _ShiftListItem extends StatelessWidget {
required this.status,
required this.statusColor,
});
/// The shift role name.
final String title;
/// The shift location or ID.
final String location;
/// The formatted time range string.
final String time;
/// The workers ratio string (e.g. "3/5").
final String workers;
/// The rate string.
final String rate;
/// The status label text.
final String status;
/// The color for the status badge.
final Color statusColor;
@override
@@ -508,11 +467,7 @@ class _ShiftListItem extends StatelessWidget {
children: <Widget>[
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: UiColors.textPrimary,
),
style: UiTypography.body2b,
),
const SizedBox(height: 4),
Row(
@@ -526,8 +481,7 @@ class _ShiftListItem extends StatelessWidget {
Expanded(
child: Text(
location,
style: const TextStyle(
fontSize: 11,
style: UiTypography.titleUppercase4m.copyWith(
color: UiColors.textSecondary,
),
maxLines: 1,
@@ -548,10 +502,8 @@ class _ShiftListItem extends StatelessWidget {
),
child: Text(
status.toUpperCase(),
style: TextStyle(
style: UiTypography.footnote2b.copyWith(
color: statusColor,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
@@ -585,6 +537,7 @@ class _ShiftListItem extends StatelessWidget {
);
}
/// Builds a small info item with icon, label, and value.
Widget _infoItem(
BuildContext context, IconData icon, String label, String value) {
return Row(
@@ -596,13 +549,13 @@ class _ShiftListItem extends StatelessWidget {
children: <Widget>[
Text(
label,
style: const TextStyle(fontSize: 10, color: UiColors.pinInactive),
style: UiTypography.footnote2r.copyWith(
color: UiColors.textInactive,
),
),
Text(
value,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
style: UiTypography.titleUppercase4b.copyWith(
color: UiColors.textDescription,
),
),
@@ -612,4 +565,3 @@ class _ShiftListItem extends StatelessWidget {
);
}
}

View File

@@ -100,12 +100,13 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
);
}
/// Builds the gradient header with back button and title.
Widget _buildHeader(BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 60, left: 20, right: 20, bottom: 40),
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: <Color>[UiColors.primary, Color(0xFF0020A0)],
colors: <Color>[UiColors.primary, UiColors.buttonPrimaryHover],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
@@ -150,6 +151,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
);
}
/// Builds the 2x2 metrics grid.
Widget _buildMetricsGrid(BuildContext context, ForecastReport report) {
final TranslationsClientReportsForecastReportEn t =
context.t.client_reports.forecast_report;
@@ -186,8 +188,8 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
label: t.metrics.total_shifts,
value: report.totalShifts.toString(),
badgeText: t.badges.scheduled,
iconColor: const Color(0xFF9333EA),
badgeColor: const Color(0xFFF3E8FF),
iconColor: UiColors.primary,
badgeColor: UiColors.tagInProgress,
),
_MetricCard(
icon: UiIcons.users,
@@ -201,6 +203,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
);
}
/// Builds the chart section with weekly spend trend.
Widget _buildChartSection(BuildContext context, ForecastReport report) {
return Container(
height: 320,
@@ -231,13 +234,14 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
for (int i = 0; i < report.weeks.length; i++) ...<Widget>[
Text('W${i + 1}',
style: const TextStyle(
color: UiColors.textSecondary, fontSize: 12)),
Text(
'W${i + 1}',
style: UiTypography.body3r.copyWith(
color: UiColors.textSecondary,
),
),
if (i < report.weeks.length - 1)
const Text('',
style: TextStyle(
color: UiColors.transparent, fontSize: 12)),
const SizedBox.shrink(),
],
],
),
@@ -247,6 +251,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
}
}
/// Metric card widget for the forecast grid.
class _MetricCard extends StatelessWidget {
const _MetricCard({
required this.icon,
@@ -257,11 +262,22 @@ class _MetricCard extends StatelessWidget {
required this.badgeColor,
});
/// The metric icon.
final IconData icon;
/// The metric label text.
final String label;
/// The metric value text.
final String value;
/// The badge text.
final String badgeText;
/// The icon tint color.
final Color iconColor;
/// The badge background color.
final Color badgeColor;
@override
@@ -308,11 +324,7 @@ class _MetricCard extends StatelessWidget {
),
child: Text(
badgeText,
style: UiTypography.footnote1r.copyWith(
color: UiColors.textPrimary,
fontSize: 10,
fontWeight: FontWeight.w600,
),
style: UiTypography.footnote2b,
),
),
],
@@ -328,7 +340,10 @@ class _WeeklyBreakdownItem extends StatelessWidget {
required this.weekIndex,
});
/// The forecast week data.
final ForecastWeek week;
/// The 1-based week index.
final int weekIndex;
@override
@@ -386,6 +401,7 @@ class _WeeklyBreakdownItem extends StatelessWidget {
);
}
/// Builds a label/value stat column.
Widget _buildStat(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -402,6 +418,7 @@ class _WeeklyBreakdownItem extends StatelessWidget {
class _ForecastChart extends StatelessWidget {
const _ForecastChart({required this.weeks});
/// The weekly forecast data points.
final List<ForecastWeek> weeks;
@override

View File

@@ -1,19 +1,19 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/presentation/blocs/no_show/no_show_bloc.dart';
import 'package:client_reports/src/presentation/blocs/no_show/no_show_event.dart';
import 'package:client_reports/src/presentation/blocs/no_show/no_show_state.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
/// Page displaying the no-show report with summary metrics and worker cards.
class NoShowReportPage extends StatefulWidget {
/// Creates a [NoShowReportPage].
const NoShowReportPage({super.key});
@override
@@ -26,7 +26,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
return BlocProvider<NoShowBloc>(
create: (BuildContext context) => Modular.get<NoShowBloc>()
..add(LoadNoShowReport(startDate: _startDate, endDate: _endDate)),
child: Scaffold(
@@ -90,16 +90,13 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
children: <Widget>[
Text(
context.t.client_reports.no_show_report.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
style: UiTypography.title1b.copyWith(
color: UiColors.white,
),
),
Text(
context.t.client_reports.no_show_report.subtitle,
style: TextStyle(
fontSize: 12,
style: UiTypography.body3r.copyWith(
color: UiColors.white.withOpacity(0.6),
),
),
@@ -107,47 +104,6 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
),
],
),
// Export button
/*
GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Export coming soon'),
duration: Duration(seconds: 2),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(
UiIcons.download,
size: 14,
color: Color(0xFF1A1A2E),
),
SizedBox(width: 6),
Text(
'Export',
style: TextStyle(
color: Color(0xFF1A1A2E),
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
*/
],
),
),
@@ -159,7 +115,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 3-chip summary row (matches prototype)
// 3-chip summary row
Row(
children: <Widget>[
Expanded(
@@ -198,9 +154,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
Text(
context.t.client_reports.no_show_report
.workers_list_title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
style: UiTypography.body3b.copyWith(
color: UiColors.textSecondary,
letterSpacing: 1.2,
),
@@ -214,7 +168,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
alignment: Alignment.center,
child: Text(
context.t.client_reports.no_show_report.empty_state,
style: const TextStyle(
style: UiTypography.body2r.copyWith(
color: UiColors.textSecondary,
),
),
@@ -241,18 +195,25 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
}
}
// Summary chip (top 3 stats)
/// Summary chip showing a single metric with icon.
class _SummaryChip extends StatelessWidget {
const _SummaryChip({
required this.icon,
required this.iconColor,
required this.label,
required this.value,
});
/// The icon to display.
final IconData icon;
/// The icon and label color.
final Color iconColor;
/// The metric label text.
final String label;
/// The metric value text.
final String value;
@override
@@ -280,10 +241,8 @@ class _SummaryChip extends StatelessWidget {
Expanded(
child: Text(
label,
style: TextStyle(
fontSize: 10,
style: UiTypography.footnote2b.copyWith(
color: iconColor,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
@@ -293,11 +252,7 @@ class _SummaryChip extends StatelessWidget {
const SizedBox(height: 8),
Text(
value,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.display1b,
),
],
),
@@ -305,24 +260,28 @@ class _SummaryChip extends StatelessWidget {
}
}
// Worker card with risk badge + latest incident ””””””””””””””
/// Worker card with risk badge and latest incident date.
class _WorkerCard extends StatelessWidget {
const _WorkerCard({required this.worker});
/// The worker item data.
final NoShowWorkerItem worker;
/// Returns the localized risk label.
String _riskLabel(BuildContext context, String riskStatus) {
if (riskStatus == 'HIGH') return context.t.client_reports.no_show_report.risks.high;
if (riskStatus == 'MEDIUM') return context.t.client_reports.no_show_report.risks.medium;
return context.t.client_reports.no_show_report.risks.low;
}
/// Returns the color for the given risk status.
Color _riskColor(String riskStatus) {
if (riskStatus == 'HIGH') return UiColors.error;
if (riskStatus == 'MEDIUM') return UiColors.textWarning;
return UiColors.success;
}
/// Returns the background color for the given risk status.
Color _riskBg(String riskStatus) {
if (riskStatus == 'HIGH') return UiColors.tagError;
if (riskStatus == 'MEDIUM') return UiColors.tagPending;
@@ -374,16 +333,11 @@ class _WorkerCard extends StatelessWidget {
children: <Widget>[
Text(
worker.staffName,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: UiColors.textPrimary,
),
style: UiTypography.body2b,
),
Text(
context.t.client_reports.no_show_report.no_show_count(count: worker.incidentCount.toString()),
style: const TextStyle(
fontSize: 12,
style: UiTypography.body3r.copyWith(
color: UiColors.textSecondary,
),
),
@@ -403,9 +357,7 @@ class _WorkerCard extends StatelessWidget {
),
child: Text(
riskLabel,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
style: UiTypography.titleUppercase4b.copyWith(
color: riskColor,
),
),
@@ -420,8 +372,7 @@ class _WorkerCard extends StatelessWidget {
children: <Widget>[
Text(
context.t.client_reports.no_show_report.latest_incident,
style: const TextStyle(
fontSize: 11,
style: UiTypography.titleUppercase4m.copyWith(
color: UiColors.textSecondary,
),
),
@@ -430,10 +381,8 @@ class _WorkerCard extends StatelessWidget {
? DateFormat('MMM dd, yyyy')
.format(worker.incidents.first.date)
: '-',
style: const TextStyle(
fontSize: 11,
style: UiTypography.titleUppercase4m.copyWith(
color: UiColors.textSecondary,
fontWeight: FontWeight.w500,
),
),
],
@@ -443,6 +392,3 @@ class _WorkerCard extends StatelessWidget {
);
}
}
// Insight line

View File

@@ -1,6 +1,7 @@
import 'package:client_reports/src/presentation/blocs/performance/performance_bloc.dart';
import 'package:client_reports/src/presentation/blocs/performance/performance_bloc.dart';
import 'package:client_reports/src/presentation/blocs/performance/performance_event.dart';
import 'package:client_reports/src/presentation/blocs/performance/performance_state.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
@@ -9,9 +10,9 @@ import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:client_reports/src/presentation/widgets/report_detail_skeleton.dart';
/// Page displaying the performance report with overall score and KPI breakdown.
class PerformanceReportPage extends StatefulWidget {
/// Creates a [PerformanceReportPage].
const PerformanceReportPage({super.key});
@override
@@ -102,18 +103,18 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
),
_KpiData(
icon: UiIcons.clock,
iconColor: const Color(0xFF9B59B6),
iconColor: UiColors.primary,
label: context.t.client_reports.performance_report.kpis.on_time_rate,
target: context.t.client_reports.performance_report.kpis.target_percent(percent: '97'),
value: onTimeRate,
displayValue: '${onTimeRate.toStringAsFixed(0)}%',
barColor: const Color(0xFF9B59B6),
barColor: UiColors.primary,
met: onTimeRate >= 97,
close: onTimeRate >= 92,
),
_KpiData(
icon: UiIcons.trendingUp,
iconColor: const Color(0xFFF39C12),
iconColor: UiColors.textWarning,
label: context.t.client_reports.performance_report.kpis.avg_fill_time,
target: context.t.client_reports.performance_report.kpis.target_hours(hours: '3'),
value: avgFillTimeHours == 0
@@ -121,7 +122,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
: (3 / avgFillTimeHours * 100).clamp(0, 100),
displayValue:
'${avgFillTimeHours.toStringAsFixed(1)} hrs',
barColor: const Color(0xFFF39C12),
barColor: UiColors.textWarning,
met: avgFillTimeHours <= 3,
close: avgFillTimeHours <= 4,
),
@@ -173,17 +174,14 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
Text(
context.t.client_reports.performance_report
.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
style: UiTypography.title1b.copyWith(
color: UiColors.white,
),
),
Text(
context.t.client_reports.performance_report
.subtitle,
style: TextStyle(
fontSize: 12,
style: UiTypography.body3r.copyWith(
color: UiColors.white.withOpacity(0.7),
),
),
@@ -191,49 +189,11 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
),
],
),
// Export
/*
GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Export coming soon'),
duration: Duration(seconds: 2),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Row(
children: [
Icon(UiIcons.download,
size: 14, color: UiColors.primary),
SizedBox(width: 6),
Text(
'Export',
style: TextStyle(
color: UiColors.primary,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
*/
],
),
),
// Content ”””””””””””””””””””””
// Content
Transform.translate(
offset: const Offset(0, -16),
child: Padding(
@@ -248,7 +208,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
horizontal: 20,
),
decoration: BoxDecoration(
color: const Color(0xFFF0F4FF),
color: UiColors.tagInProgress,
borderRadius: BorderRadius.circular(16),
boxShadow: <BoxShadow>[
BoxShadow(
@@ -268,17 +228,14 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
const SizedBox(height: 12),
Text(
context.t.client_reports.performance_report.overall_score.title,
style: const TextStyle(
fontSize: 13,
style: UiTypography.body3m.copyWith(
color: UiColors.textSecondary,
),
),
const SizedBox(height: 8),
Text(
'${overallScore.toStringAsFixed(0)}/100',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
style: UiTypography.secondaryDisplay2b.copyWith(
color: UiColors.primary,
),
),
@@ -294,9 +251,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
),
child: Text(
scoreLabel,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
style: UiTypography.body3b.copyWith(
color: scoreLabelColor,
),
),
@@ -325,9 +280,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
children: <Widget>[
Text(
context.t.client_reports.performance_report.kpis_title,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
style: UiTypography.titleUppercase4b.copyWith(
color: UiColors.textSecondary,
letterSpacing: 1.2,
),
@@ -357,9 +310,8 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
}
}
// ” KPI data model ””””””””””””””””””””””””””””””
/// Data model for a single KPI row.
class _KpiData {
const _KpiData({
required this.icon,
required this.iconColor,
@@ -371,21 +323,40 @@ class _KpiData {
required this.met,
required this.close,
});
/// The KPI icon.
final IconData icon;
/// The icon tint color.
final Color iconColor;
/// The KPI label text.
final String label;
/// The target description text.
final String target;
final double value; // 0-100 for bar
/// The KPI value (0-100) for the progress bar.
final double value;
/// The formatted display value string.
final String displayValue;
/// The progress bar color.
final Color barColor;
/// Whether the KPI target has been met.
final bool met;
/// Whether the KPI is close to the target.
final bool close;
}
// ” KPI row widget ””””””””””””””””””””””””””””””
/// Widget rendering a single KPI row with label, progress bar, and badge.
class _KpiRow extends StatelessWidget {
const _KpiRow({required this.kpi});
/// The KPI data to render.
final _KpiData kpi;
@override
@@ -428,33 +399,24 @@ class _KpiRow extends StatelessWidget {
children: <Widget>[
Text(
kpi.label,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UiColors.textPrimary,
),
style: UiTypography.body3m,
),
Text(
kpi.target,
style: const TextStyle(
fontSize: 11,
style: UiTypography.titleUppercase4m.copyWith(
color: UiColors.textSecondary,
),
),
],
),
),
// Value + badge inline (matches prototype)
// Value + badge inline
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
kpi.displayValue,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.body1b,
),
const SizedBox(width: 6),
Container(
@@ -468,9 +430,7 @@ class _KpiRow extends StatelessWidget {
),
child: Text(
badgeText,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
style: UiTypography.footnote2b.copyWith(
color: badgeColor,
),
),
@@ -494,4 +454,3 @@ class _KpiRow extends StatelessWidget {
);
}
}

View File

@@ -97,16 +97,13 @@ class _SpendReportPageState extends State<SpendReportPage> {
children: <Widget>[
Text(
context.t.client_reports.spend_report.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
style: UiTypography.title1b.copyWith(
color: UiColors.white,
),
),
Text(
context.t.client_reports.spend_report.subtitle,
style: TextStyle(
fontSize: 12,
style: UiTypography.body3r.copyWith(
color: UiColors.white.withOpacity(0.7),
),
),
@@ -179,11 +176,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
Text(
context.t.client_reports.spend_report
.chart_title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.body2b,
),
const SizedBox(height: 32),
Expanded(
@@ -222,6 +215,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
class _SpendBarChart extends StatelessWidget {
const _SpendBarChart({required this.chartData});
/// The chart data points to render.
final List<SpendDataPoint> chartData;
@override
@@ -245,9 +239,8 @@ class _SpendBarChart extends StatelessWidget {
BarChartRodData rod, int rodIndex) {
return BarTooltipItem(
'\$${rod.toY.round()}',
const TextStyle(
UiTypography.body2b.copyWith(
color: UiColors.white,
fontWeight: FontWeight.bold,
),
);
},
@@ -269,9 +262,8 @@ class _SpendBarChart extends StatelessWidget {
space: 8,
child: Text(
DateFormat('E').format(date),
style: const TextStyle(
style: UiTypography.titleUppercase4m.copyWith(
color: UiColors.textSecondary,
fontSize: 11,
),
),
);
@@ -288,9 +280,8 @@ class _SpendBarChart extends StatelessWidget {
axisSide: meta.axisSide,
child: Text(
'\$${(value / 1000).toStringAsFixed(0)}k',
style: const TextStyle(
style: UiTypography.footnote2r.copyWith(
color: UiColors.textSecondary,
fontSize: 10,
),
),
);
@@ -333,6 +324,7 @@ class _SpendBarChart extends StatelessWidget {
}
}
/// Stat card showing a spend metric with icon, value, and pill badge.
class _SpendStatCard extends StatelessWidget {
const _SpendStatCard({
required this.label,
@@ -342,10 +334,19 @@ class _SpendStatCard extends StatelessWidget {
required this.icon,
});
/// The metric label text.
final String label;
/// The metric value text.
final String value;
/// The pill badge text.
final String pillText;
/// The theme color for the icon and pill.
final Color themeColor;
/// The icon to display.
final IconData icon;
@override
@@ -373,10 +374,8 @@ class _SpendStatCard extends StatelessWidget {
Expanded(
child: Text(
label,
style: const TextStyle(
fontSize: 12,
style: UiTypography.body3m.copyWith(
color: UiColors.textSecondary,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -387,11 +386,7 @@ class _SpendStatCard extends StatelessWidget {
const SizedBox(height: 12),
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.headline1b,
),
const SizedBox(height: 12),
Container(
@@ -402,9 +397,7 @@ class _SpendStatCard extends StatelessWidget {
),
child: Text(
pillText,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
style: UiTypography.footnote2b.copyWith(
color: themeColor,
),
),
@@ -419,6 +412,7 @@ class _SpendStatCard extends StatelessWidget {
class _SpendByCategoryCard extends StatelessWidget {
const _SpendByCategoryCard({required this.categories});
/// The category breakdown items.
final List<SpendItem> categories;
@override
@@ -441,11 +435,7 @@ class _SpendByCategoryCard extends StatelessWidget {
children: <Widget>[
Text(
context.t.client_reports.spend_report.spend_by_industry,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.body2b,
),
const SizedBox(height: 24),
if (categories.isEmpty)
@@ -454,7 +444,9 @@ class _SpendByCategoryCard extends StatelessWidget {
padding: const EdgeInsets.all(16.0),
child: Text(
context.t.client_reports.spend_report.no_industry_data,
style: const TextStyle(color: UiColors.textSecondary),
style: UiTypography.body2r.copyWith(
color: UiColors.textSecondary,
),
),
),
)
@@ -469,8 +461,7 @@ class _SpendByCategoryCard extends StatelessWidget {
children: <Widget>[
Text(
item.category,
style: const TextStyle(
fontSize: 13,
style: UiTypography.body3m.copyWith(
color: UiColors.textSecondary,
),
),
@@ -478,11 +469,7 @@ class _SpendByCategoryCard extends StatelessWidget {
NumberFormat.currency(
symbol: r'$', decimalDigits: 0)
.format(item.amountCents / 100),
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.body3b,
),
],
),
@@ -500,8 +487,7 @@ class _SpendByCategoryCard extends StatelessWidget {
Text(
context.t.client_reports.spend_report.percent_total(
percent: item.percentage.toStringAsFixed(1)),
style: const TextStyle(
fontSize: 10,
style: UiTypography.footnote2r.copyWith(
color: UiColors.textDescription,
),
),

View File

@@ -75,11 +75,7 @@ class MetricCard extends StatelessWidget {
children: <Widget>[
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: UiColors.textPrimary,
),
style: UiTypography.headline1b,
),
const SizedBox(height: 4),
Container(

View File

@@ -49,8 +49,9 @@ class MetricsGrid extends StatelessWidget {
Expanded(
child: Text(
state.message,
style:
const TextStyle(color: UiColors.error, fontSize: 12),
style: UiTypography.body3r.copyWith(
color: UiColors.error,
),
),
),
],

View File

@@ -1,10 +1,8 @@
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
import 'package:client_reports/src/presentation/widgets/reports_page/report_card.dart';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'report_card.dart';
/// A section displaying quick access report cards.
///
/// Shows 4 quick report cards for:

View File

@@ -69,11 +69,7 @@ class ReportCard extends StatelessWidget {
children: <Widget>[
Text(
name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UiColors.textPrimary,
),
style: UiTypography.body2m,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -88,8 +84,7 @@ class ReportCard extends StatelessWidget {
const SizedBox(width: 4),
Text(
context.t.client_reports.quick_reports.two_click_export,
style: const TextStyle(
fontSize: 12,
style: UiTypography.body3r.copyWith(
color: UiColors.textSecondary,
),
),

View File

@@ -64,9 +64,7 @@ class ReportsHeader extends StatelessWidget {
const SizedBox(width: 12),
Text(
context.t.client_reports.title,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
style: UiTypography.headline3b.copyWith(
color: UiColors.white,
),
),
@@ -98,12 +96,9 @@ class ReportsHeader extends StatelessWidget {
),
labelColor: UiColors.primary,
unselectedLabelColor: UiColors.white,
labelStyle: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
labelStyle: UiTypography.body2m,
indicatorSize: TabBarIndicatorSize.tab,
dividerColor: Colors.transparent,
dividerColor: UiColors.transparent,
tabs: <Widget>[
Tab(text: context.t.client_reports.tabs.today),
Tab(text: context.t.client_reports.tabs.week),

View File

@@ -1,5 +1,12 @@
import 'package:client_reports/src/data/repositories_impl/reports_repository_impl.dart';
import 'package:client_reports/src/domain/repositories/reports_repository.dart';
import 'package:client_reports/src/domain/usecases/get_coverage_report_usecase.dart';
import 'package:client_reports/src/domain/usecases/get_daily_ops_report_usecase.dart';
import 'package:client_reports/src/domain/usecases/get_forecast_report_usecase.dart';
import 'package:client_reports/src/domain/usecases/get_no_show_report_usecase.dart';
import 'package:client_reports/src/domain/usecases/get_performance_report_usecase.dart';
import 'package:client_reports/src/domain/usecases/get_reports_summary_usecase.dart';
import 'package:client_reports/src/domain/usecases/get_spend_report_usecase.dart';
import 'package:client_reports/src/presentation/blocs/coverage/coverage_bloc.dart';
import 'package:client_reports/src/presentation/blocs/daily_ops/daily_ops_bloc.dart';
import 'package:client_reports/src/presentation/blocs/forecast/forecast_bloc.dart';
@@ -25,16 +32,84 @@ class ReportsModule extends Module {
@override
void binds(Injector i) {
i.addLazySingleton<ReportsRepository>(
// ── Repository ───────────────────────────────────────────────────────────
i.addLazySingleton<ReportsRepositoryInterface>(
() => ReportsRepositoryImpl(apiService: i.get<BaseApiService>()),
);
i.add<DailyOpsBloc>(DailyOpsBloc.new);
i.add<SpendBloc>(SpendBloc.new);
i.add<CoverageBloc>(CoverageBloc.new);
i.add<ForecastBloc>(ForecastBloc.new);
i.add<PerformanceBloc>(PerformanceBloc.new);
i.add<NoShowBloc>(NoShowBloc.new);
i.add<ReportsSummaryBloc>(ReportsSummaryBloc.new);
// ── Use Cases ────────────────────────────────────────────────────────────
i.add<GetDailyOpsReportUseCase>(
() => GetDailyOpsReportUseCase(
i.get<ReportsRepositoryInterface>(),
),
);
i.add<GetSpendReportUseCase>(
() => GetSpendReportUseCase(
i.get<ReportsRepositoryInterface>(),
),
);
i.add<GetCoverageReportUseCase>(
() => GetCoverageReportUseCase(
i.get<ReportsRepositoryInterface>(),
),
);
i.add<GetForecastReportUseCase>(
() => GetForecastReportUseCase(
i.get<ReportsRepositoryInterface>(),
),
);
i.add<GetPerformanceReportUseCase>(
() => GetPerformanceReportUseCase(
i.get<ReportsRepositoryInterface>(),
),
);
i.add<GetNoShowReportUseCase>(
() => GetNoShowReportUseCase(
i.get<ReportsRepositoryInterface>(),
),
);
i.add<GetReportsSummaryUseCase>(
() => GetReportsSummaryUseCase(
i.get<ReportsRepositoryInterface>(),
),
);
// ── BLoCs ────────────────────────────────────────────────────────────────
i.add<DailyOpsBloc>(
() => DailyOpsBloc(
getDailyOpsReportUseCase: i.get<GetDailyOpsReportUseCase>(),
),
);
i.add<SpendBloc>(
() => SpendBloc(
getSpendReportUseCase: i.get<GetSpendReportUseCase>(),
),
);
i.add<CoverageBloc>(
() => CoverageBloc(
getCoverageReportUseCase: i.get<GetCoverageReportUseCase>(),
),
);
i.add<ForecastBloc>(
() => ForecastBloc(
getForecastReportUseCase: i.get<GetForecastReportUseCase>(),
),
);
i.add<PerformanceBloc>(
() => PerformanceBloc(
getPerformanceReportUseCase: i.get<GetPerformanceReportUseCase>(),
),
);
i.add<NoShowBloc>(
() => NoShowBloc(
getNoShowReportUseCase: i.get<GetNoShowReportUseCase>(),
),
);
i.add<ReportsSummaryBloc>(
() => ReportsSummaryBloc(
getReportsSummaryUseCase: i.get<GetReportsSummaryUseCase>(),
),
);
}
@override