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:
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user