feat: Migrate staff profile features from Data Connect to V2 REST API
- Removed data_connect package from mobile pubspec.yaml. - Added documentation for V2 profile migration status and QA findings. - Implemented new session management with ClientSessionStore and StaffSessionStore. - Created V2SessionService for handling user sessions via the V2 API. - Developed use cases for cancelling late worker assignments and submitting worker reviews. - Added arguments and use cases for payment chart retrieval and profile completion checks. - Implemented repository interfaces and their implementations for staff main and profile features. - Ensured proper error handling and validation in use cases.
This commit is contained in:
@@ -1,26 +1,35 @@
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'data/repositories_impl/coverage_repository_impl.dart';
|
||||
import 'domain/repositories/coverage_repository.dart';
|
||||
import 'domain/usecases/get_coverage_stats_usecase.dart';
|
||||
import 'domain/usecases/get_shifts_for_date_usecase.dart';
|
||||
import 'presentation/blocs/coverage_bloc.dart';
|
||||
import 'presentation/pages/coverage_page.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import 'package:client_coverage/src/data/repositories_impl/coverage_repository_impl.dart';
|
||||
import 'package:client_coverage/src/domain/repositories/coverage_repository.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/cancel_late_worker_usecase.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/get_coverage_stats_usecase.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/get_shifts_for_date_usecase.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/submit_worker_review_usecase.dart';
|
||||
import 'package:client_coverage/src/presentation/blocs/coverage_bloc.dart';
|
||||
import 'package:client_coverage/src/presentation/pages/coverage_page.dart';
|
||||
|
||||
/// Modular module for the coverage feature.
|
||||
///
|
||||
/// Uses the V2 REST API via [BaseApiService] for all backend access.
|
||||
class CoverageModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => <Module>[DataConnectModule()];
|
||||
List<Module> get imports => <Module>[CoreModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.addLazySingleton<CoverageRepository>(CoverageRepositoryImpl.new);
|
||||
i.addLazySingleton<CoverageRepository>(
|
||||
() => CoverageRepositoryImpl(apiService: i.get<BaseApiService>()),
|
||||
);
|
||||
|
||||
// Use Cases
|
||||
i.addLazySingleton(GetShiftsForDateUseCase.new);
|
||||
i.addLazySingleton(GetCoverageStatsUseCase.new);
|
||||
i.addLazySingleton(SubmitWorkerReviewUseCase.new);
|
||||
i.addLazySingleton(CancelLateWorkerUseCase.new);
|
||||
|
||||
// BLoCs
|
||||
i.addLazySingleton<CoverageBloc>(CoverageBloc.new);
|
||||
@@ -28,7 +37,9 @@ class CoverageModule extends Module {
|
||||
|
||||
@override
|
||||
void routes(RouteManager r) {
|
||||
r.child(ClientPaths.childRoute(ClientPaths.coverage, ClientPaths.coverage),
|
||||
child: (_) => const CoveragePage());
|
||||
r.child(
|
||||
ClientPaths.childRoute(ClientPaths.coverage, ClientPaths.coverage),
|
||||
child: (_) => const CoveragePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,89 @@
|
||||
// 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_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/coverage_repository.dart';
|
||||
|
||||
/// Implementation of [CoverageRepository] that delegates to [dc.CoverageConnectorRepository].
|
||||
import 'package:client_coverage/src/domain/repositories/coverage_repository.dart';
|
||||
|
||||
/// V2 API implementation of [CoverageRepository].
|
||||
///
|
||||
/// This implementation follows the "Buffer Layer" pattern by using a dedicated
|
||||
/// connector repository from the data_connect package.
|
||||
/// Uses [BaseApiService] with [V2ApiEndpoints] for all backend access.
|
||||
class CoverageRepositoryImpl implements CoverageRepository {
|
||||
/// Creates a [CoverageRepositoryImpl].
|
||||
CoverageRepositoryImpl({required BaseApiService apiService})
|
||||
: _apiService = apiService;
|
||||
|
||||
CoverageRepositoryImpl({
|
||||
dc.CoverageConnectorRepository? connectorRepository,
|
||||
dc.DataConnectService? service,
|
||||
}) : _connectorRepository = connectorRepository ??
|
||||
dc.DataConnectService.instance.getCoverageRepository(),
|
||||
_service = service ?? dc.DataConnectService.instance;
|
||||
final dc.CoverageConnectorRepository _connectorRepository;
|
||||
final dc.DataConnectService _service;
|
||||
final BaseApiService _apiService;
|
||||
|
||||
@override
|
||||
Future<List<CoverageShift>> getShiftsForDate({required DateTime date}) async {
|
||||
final String businessId = await _service.getBusinessId();
|
||||
return _connectorRepository.getShiftsForDate(
|
||||
businessId: businessId,
|
||||
date: date,
|
||||
Future<List<ShiftWithWorkers>> getShiftsForDate({
|
||||
required DateTime date,
|
||||
}) async {
|
||||
final String dateStr =
|
||||
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
final ApiResponse response = await _apiService.get(
|
||||
V2ApiEndpoints.clientCoverage,
|
||||
params: <String, dynamic>{'date': dateStr},
|
||||
);
|
||||
final List<dynamic> items = response.data['items'] as List<dynamic>;
|
||||
return items
|
||||
.map((dynamic e) =>
|
||||
ShiftWithWorkers.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CoverageStats> getCoverageStats({required DateTime date}) async {
|
||||
final List<CoverageShift> shifts = await getShiftsForDate(date: date);
|
||||
|
||||
final int totalNeeded = shifts.fold<int>(
|
||||
0,
|
||||
(int sum, CoverageShift shift) => sum + shift.workersNeeded,
|
||||
final String dateStr =
|
||||
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
final ApiResponse response = await _apiService.get(
|
||||
V2ApiEndpoints.clientCoverageStats,
|
||||
params: <String, dynamic>{'date': dateStr},
|
||||
);
|
||||
return CoverageStats.fromJson(response.data as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
final List<CoverageWorker> allWorkers =
|
||||
shifts.expand((CoverageShift shift) => shift.workers).toList();
|
||||
final int totalConfirmed = allWorkers.length;
|
||||
final int checkedIn = allWorkers
|
||||
.where((CoverageWorker w) => w.status == CoverageWorkerStatus.checkedIn)
|
||||
.length;
|
||||
final int enRoute = allWorkers
|
||||
.where((CoverageWorker w) =>
|
||||
w.status == CoverageWorkerStatus.confirmed && w.checkInTime == null)
|
||||
.length;
|
||||
final int late = allWorkers
|
||||
.where((CoverageWorker w) => w.status == CoverageWorkerStatus.late)
|
||||
.length;
|
||||
@override
|
||||
Future<void> submitWorkerReview({
|
||||
required String staffId,
|
||||
required int rating,
|
||||
String? assignmentId,
|
||||
String? feedback,
|
||||
List<String>? issueFlags,
|
||||
bool? markAsFavorite,
|
||||
}) async {
|
||||
final Map<String, dynamic> body = <String, dynamic>{
|
||||
'staffId': staffId,
|
||||
'rating': rating,
|
||||
};
|
||||
if (assignmentId != null) {
|
||||
body['assignmentId'] = assignmentId;
|
||||
}
|
||||
if (feedback != null) {
|
||||
body['feedback'] = feedback;
|
||||
}
|
||||
if (issueFlags != null && issueFlags.isNotEmpty) {
|
||||
body['issueFlags'] = issueFlags;
|
||||
}
|
||||
if (markAsFavorite != null) {
|
||||
body['markAsFavorite'] = markAsFavorite;
|
||||
}
|
||||
await _apiService.post(
|
||||
V2ApiEndpoints.clientCoverageReviews,
|
||||
data: body,
|
||||
);
|
||||
}
|
||||
|
||||
return CoverageStats(
|
||||
totalNeeded: totalNeeded,
|
||||
totalConfirmed: totalConfirmed,
|
||||
checkedIn: checkedIn,
|
||||
enRoute: enRoute,
|
||||
late: late,
|
||||
@override
|
||||
Future<void> cancelLateWorker({
|
||||
required String assignmentId,
|
||||
String? reason,
|
||||
}) async {
|
||||
final Map<String, dynamic> body = <String, dynamic>{};
|
||||
if (reason != null) {
|
||||
body['reason'] = reason;
|
||||
}
|
||||
await _apiService.post(
|
||||
V2ApiEndpoints.clientCoverageCancelLateWorker(assignmentId),
|
||||
data: body,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// Arguments for cancelling a late worker's assignment.
|
||||
class CancelLateWorkerArguments extends UseCaseArgument {
|
||||
/// Creates [CancelLateWorkerArguments].
|
||||
const CancelLateWorkerArguments({
|
||||
required this.assignmentId,
|
||||
this.reason,
|
||||
});
|
||||
|
||||
/// The assignment ID to cancel.
|
||||
final String assignmentId;
|
||||
|
||||
/// Optional cancellation reason.
|
||||
final String? reason;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[assignmentId, reason];
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// Arguments for fetching coverage statistics for a specific date.
|
||||
///
|
||||
/// This argument class encapsulates the date parameter required by
|
||||
/// the [GetCoverageStatsUseCase].
|
||||
class GetCoverageStatsArguments extends UseCaseArgument {
|
||||
/// Creates [GetCoverageStatsArguments].
|
||||
const GetCoverageStatsArguments({required this.date});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// Arguments for fetching shifts for a specific date.
|
||||
///
|
||||
/// This argument class encapsulates the date parameter required by
|
||||
/// the [GetShiftsForDateUseCase].
|
||||
class GetShiftsForDateArguments extends UseCaseArgument {
|
||||
/// Creates [GetShiftsForDateArguments].
|
||||
const GetShiftsForDateArguments({required this.date});
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// Arguments for submitting a worker review from the coverage page.
|
||||
class SubmitWorkerReviewArguments extends UseCaseArgument {
|
||||
/// Creates [SubmitWorkerReviewArguments].
|
||||
const SubmitWorkerReviewArguments({
|
||||
required this.staffId,
|
||||
required this.rating,
|
||||
this.assignmentId,
|
||||
this.feedback,
|
||||
this.issueFlags,
|
||||
this.markAsFavorite,
|
||||
});
|
||||
|
||||
/// The ID of the worker being reviewed.
|
||||
final String staffId;
|
||||
|
||||
/// The rating value (1-5).
|
||||
final int rating;
|
||||
|
||||
/// The assignment ID, if reviewing for a specific assignment.
|
||||
final String? assignmentId;
|
||||
|
||||
/// Optional text feedback.
|
||||
final String? feedback;
|
||||
|
||||
/// Optional list of issue flag labels.
|
||||
final List<String>? issueFlags;
|
||||
|
||||
/// Whether to mark/unmark the worker as a favorite.
|
||||
final bool? markAsFavorite;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
staffId,
|
||||
rating,
|
||||
assignmentId,
|
||||
feedback,
|
||||
issueFlags,
|
||||
markAsFavorite,
|
||||
];
|
||||
}
|
||||
@@ -2,22 +2,35 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Repository interface for coverage-related operations.
|
||||
///
|
||||
/// This interface defines the contract for accessing coverage data,
|
||||
/// Defines the contract for accessing coverage data via the V2 REST API,
|
||||
/// acting as a boundary between the Domain and Data layers.
|
||||
/// It allows the Domain layer to remain independent of specific data sources.
|
||||
///
|
||||
/// Implementation of this interface must delegate all data access through
|
||||
/// the `packages/data_connect` layer, ensuring compliance with Clean Architecture.
|
||||
abstract interface class CoverageRepository {
|
||||
/// Fetches shifts for a specific date.
|
||||
///
|
||||
/// Returns a list of [CoverageShift] entities representing all shifts
|
||||
/// scheduled for the given [date].
|
||||
Future<List<CoverageShift>> getShiftsForDate({required DateTime date});
|
||||
/// Fetches shifts with assigned workers for a specific [date].
|
||||
Future<List<ShiftWithWorkers>> getShiftsForDate({required DateTime date});
|
||||
|
||||
/// Fetches coverage statistics for a specific date.
|
||||
///
|
||||
/// Returns [CoverageStats] containing aggregated metrics including
|
||||
/// total workers needed, confirmed, checked in, en route, and late.
|
||||
/// Fetches aggregated coverage statistics for a specific [date].
|
||||
Future<CoverageStats> getCoverageStats({required DateTime date});
|
||||
|
||||
/// Submits a worker review from the coverage page.
|
||||
///
|
||||
/// [staffId] identifies the worker being reviewed.
|
||||
/// [rating] is an integer from 1 to 5.
|
||||
/// Optional fields: [assignmentId], [feedback], [issueFlags], [markAsFavorite].
|
||||
Future<void> submitWorkerReview({
|
||||
required String staffId,
|
||||
required int rating,
|
||||
String? assignmentId,
|
||||
String? feedback,
|
||||
List<String>? issueFlags,
|
||||
bool? markAsFavorite,
|
||||
});
|
||||
|
||||
/// Cancels a late worker's assignment.
|
||||
///
|
||||
/// [assignmentId] identifies the assignment to cancel.
|
||||
/// [reason] is an optional cancellation reason.
|
||||
Future<void> cancelLateWorker({
|
||||
required String assignmentId,
|
||||
String? reason,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import 'package:client_coverage/src/domain/arguments/cancel_late_worker_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/repositories/coverage_repository.dart';
|
||||
|
||||
/// Use case for cancelling a late worker's assignment.
|
||||
///
|
||||
/// Delegates to [CoverageRepository] to cancel the assignment via V2 API.
|
||||
class CancelLateWorkerUseCase
|
||||
implements UseCase<CancelLateWorkerArguments, void> {
|
||||
/// Creates a [CancelLateWorkerUseCase].
|
||||
CancelLateWorkerUseCase(this._repository);
|
||||
|
||||
final CoverageRepository _repository;
|
||||
|
||||
@override
|
||||
Future<void> call(CancelLateWorkerArguments arguments) {
|
||||
return _repository.cancelLateWorker(
|
||||
assignmentId: arguments.assignmentId,
|
||||
reason: arguments.reason,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,12 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../arguments/get_coverage_stats_arguments.dart';
|
||||
import '../repositories/coverage_repository.dart';
|
||||
import 'package:client_coverage/src/domain/arguments/get_coverage_stats_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/repositories/coverage_repository.dart';
|
||||
|
||||
/// Use case for fetching coverage statistics for a specific date.
|
||||
/// Use case for fetching aggregated coverage statistics for a specific date.
|
||||
///
|
||||
/// This use case encapsulates the logic for retrieving coverage metrics including
|
||||
/// total workers needed, confirmed, checked in, en route, and late.
|
||||
/// It delegates the data retrieval to the [CoverageRepository].
|
||||
///
|
||||
/// Follows the KROW Clean Architecture pattern by:
|
||||
/// - Extending from [UseCase] base class
|
||||
/// - Using [GetCoverageStatsArguments] for input
|
||||
/// - Returning domain entities ([CoverageStats])
|
||||
/// - Delegating to repository abstraction
|
||||
/// Delegates to [CoverageRepository] and returns a [CoverageStats] entity.
|
||||
class GetCoverageStatsUseCase
|
||||
implements UseCase<GetCoverageStatsArguments, CoverageStats> {
|
||||
/// Creates a [GetCoverageStatsUseCase].
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../arguments/get_shifts_for_date_arguments.dart';
|
||||
import '../repositories/coverage_repository.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Use case for fetching shifts for a specific date.
|
||||
import 'package:client_coverage/src/domain/arguments/get_shifts_for_date_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/repositories/coverage_repository.dart';
|
||||
|
||||
/// Use case for fetching shifts with workers for a specific date.
|
||||
///
|
||||
/// This use case encapsulates the logic for retrieving all shifts scheduled for a given date.
|
||||
/// It delegates the data retrieval to the [CoverageRepository].
|
||||
///
|
||||
/// Follows the KROW Clean Architecture pattern by:
|
||||
/// - Extending from [UseCase] base class
|
||||
/// - Using [GetShiftsForDateArguments] for input
|
||||
/// - Returning domain entities ([CoverageShift])
|
||||
/// - Delegating to repository abstraction
|
||||
/// Delegates to [CoverageRepository] and returns V2 [ShiftWithWorkers] entities.
|
||||
class GetShiftsForDateUseCase
|
||||
implements UseCase<GetShiftsForDateArguments, List<CoverageShift>> {
|
||||
implements UseCase<GetShiftsForDateArguments, List<ShiftWithWorkers>> {
|
||||
/// Creates a [GetShiftsForDateUseCase].
|
||||
GetShiftsForDateUseCase(this._repository);
|
||||
|
||||
final CoverageRepository _repository;
|
||||
|
||||
@override
|
||||
Future<List<CoverageShift>> call(GetShiftsForDateArguments arguments) {
|
||||
Future<List<ShiftWithWorkers>> call(GetShiftsForDateArguments arguments) {
|
||||
return _repository.getShiftsForDate(date: arguments.date);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import 'package:client_coverage/src/domain/arguments/submit_worker_review_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/repositories/coverage_repository.dart';
|
||||
|
||||
/// Use case for submitting a worker review from the coverage page.
|
||||
///
|
||||
/// Validates the rating range and delegates to [CoverageRepository].
|
||||
class SubmitWorkerReviewUseCase
|
||||
implements UseCase<SubmitWorkerReviewArguments, void> {
|
||||
/// Creates a [SubmitWorkerReviewUseCase].
|
||||
SubmitWorkerReviewUseCase(this._repository);
|
||||
|
||||
final CoverageRepository _repository;
|
||||
|
||||
@override
|
||||
Future<void> call(SubmitWorkerReviewArguments arguments) async {
|
||||
if (arguments.rating < 1 || arguments.rating > 5) {
|
||||
throw ArgumentError('Rating must be between 1 and 5');
|
||||
}
|
||||
return _repository.submitWorkerReview(
|
||||
staffId: arguments.staffId,
|
||||
rating: arguments.rating,
|
||||
assignmentId: arguments.assignmentId,
|
||||
feedback: arguments.feedback,
|
||||
issueFlags: arguments.issueFlags,
|
||||
markAsFavorite: arguments.markAsFavorite,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,46 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/arguments/get_coverage_stats_arguments.dart';
|
||||
import '../../domain/arguments/get_shifts_for_date_arguments.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/usecases/get_coverage_stats_usecase.dart';
|
||||
import '../../domain/usecases/get_shifts_for_date_usecase.dart';
|
||||
import 'coverage_event.dart';
|
||||
import 'coverage_state.dart';
|
||||
|
||||
import 'package:client_coverage/src/domain/arguments/cancel_late_worker_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/arguments/get_coverage_stats_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/arguments/get_shifts_for_date_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/arguments/submit_worker_review_arguments.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/cancel_late_worker_usecase.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/get_coverage_stats_usecase.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/get_shifts_for_date_usecase.dart';
|
||||
import 'package:client_coverage/src/domain/usecases/submit_worker_review_usecase.dart';
|
||||
import 'package:client_coverage/src/presentation/blocs/coverage_event.dart';
|
||||
import 'package:client_coverage/src/presentation/blocs/coverage_state.dart';
|
||||
|
||||
/// BLoC for managing coverage feature state.
|
||||
///
|
||||
/// This BLoC handles:
|
||||
/// - Loading shifts for a specific date
|
||||
/// - Loading coverage statistics
|
||||
/// - Refreshing coverage data
|
||||
/// Handles loading shifts, coverage statistics, worker reviews,
|
||||
/// and late-worker cancellation for a selected date.
|
||||
class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
|
||||
with BlocErrorHandler<CoverageState> {
|
||||
/// Creates a [CoverageBloc].
|
||||
CoverageBloc({
|
||||
required GetShiftsForDateUseCase getShiftsForDate,
|
||||
required GetCoverageStatsUseCase getCoverageStats,
|
||||
}) : _getShiftsForDate = getShiftsForDate,
|
||||
required SubmitWorkerReviewUseCase submitWorkerReview,
|
||||
required CancelLateWorkerUseCase cancelLateWorker,
|
||||
}) : _getShiftsForDate = getShiftsForDate,
|
||||
_getCoverageStats = getCoverageStats,
|
||||
_submitWorkerReview = submitWorkerReview,
|
||||
_cancelLateWorker = cancelLateWorker,
|
||||
super(const CoverageState()) {
|
||||
on<CoverageLoadRequested>(_onLoadRequested);
|
||||
on<CoverageRefreshRequested>(_onRefreshRequested);
|
||||
on<CoverageRepostShiftRequested>(_onRepostShiftRequested);
|
||||
on<CoverageSubmitReviewRequested>(_onSubmitReviewRequested);
|
||||
on<CoverageCancelLateWorkerRequested>(_onCancelLateWorkerRequested);
|
||||
}
|
||||
|
||||
final GetShiftsForDateUseCase _getShiftsForDate;
|
||||
final GetCoverageStatsUseCase _getCoverageStats;
|
||||
final SubmitWorkerReviewUseCase _submitWorkerReview;
|
||||
final CancelLateWorkerUseCase _cancelLateWorker;
|
||||
|
||||
/// Handles the load requested event.
|
||||
Future<void> _onLoadRequested(
|
||||
@@ -47,12 +58,15 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
// Fetch shifts and stats concurrently
|
||||
final List<Object> results = await Future.wait<Object>(<Future<Object>>[
|
||||
_getShiftsForDate(GetShiftsForDateArguments(date: event.date)),
|
||||
_getCoverageStats(GetCoverageStatsArguments(date: event.date)),
|
||||
]);
|
||||
final List<Object> results = await Future.wait<Object>(
|
||||
<Future<Object>>[
|
||||
_getShiftsForDate(GetShiftsForDateArguments(date: event.date)),
|
||||
_getCoverageStats(GetCoverageStatsArguments(date: event.date)),
|
||||
],
|
||||
);
|
||||
|
||||
final List<CoverageShift> shifts = results[0] as List<CoverageShift>;
|
||||
final List<ShiftWithWorkers> shifts =
|
||||
results[0] as List<ShiftWithWorkers>;
|
||||
final CoverageStats stats = results[1] as CoverageStats;
|
||||
|
||||
emit(
|
||||
@@ -86,17 +100,14 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
|
||||
CoverageRepostShiftRequested event,
|
||||
Emitter<CoverageState> emit,
|
||||
) async {
|
||||
// In a real implementation, this would call a repository method.
|
||||
// For this audit completion, we simulate the action and refresh the state.
|
||||
emit(state.copyWith(status: CoverageStatus.loading));
|
||||
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
// Simulating API call delay
|
||||
// TODO: Implement re-post shift via V2 API when endpoint is available.
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
// Since we don't have a real re-post mutation yet, we just refresh
|
||||
|
||||
if (state.selectedDate != null) {
|
||||
add(CoverageLoadRequested(date: state.selectedDate!));
|
||||
}
|
||||
@@ -107,5 +118,70 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the submit review requested event.
|
||||
Future<void> _onSubmitReviewRequested(
|
||||
CoverageSubmitReviewRequested event,
|
||||
Emitter<CoverageState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(writeStatus: CoverageWriteStatus.submitting));
|
||||
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _submitWorkerReview(
|
||||
SubmitWorkerReviewArguments(
|
||||
staffId: event.staffId,
|
||||
rating: event.rating,
|
||||
assignmentId: event.assignmentId,
|
||||
feedback: event.feedback,
|
||||
issueFlags: event.issueFlags,
|
||||
markAsFavorite: event.markAsFavorite,
|
||||
),
|
||||
);
|
||||
|
||||
emit(state.copyWith(writeStatus: CoverageWriteStatus.submitted));
|
||||
|
||||
// Refresh coverage data after successful review.
|
||||
if (state.selectedDate != null) {
|
||||
add(CoverageLoadRequested(date: state.selectedDate!));
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
writeStatus: CoverageWriteStatus.submitFailure,
|
||||
writeErrorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles the cancel late worker requested event.
|
||||
Future<void> _onCancelLateWorkerRequested(
|
||||
CoverageCancelLateWorkerRequested event,
|
||||
Emitter<CoverageState> emit,
|
||||
) async {
|
||||
emit(state.copyWith(writeStatus: CoverageWriteStatus.submitting));
|
||||
|
||||
await handleError(
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
await _cancelLateWorker(
|
||||
CancelLateWorkerArguments(
|
||||
assignmentId: event.assignmentId,
|
||||
reason: event.reason,
|
||||
),
|
||||
);
|
||||
|
||||
emit(state.copyWith(writeStatus: CoverageWriteStatus.submitted));
|
||||
|
||||
// Refresh coverage data after cancellation.
|
||||
if (state.selectedDate != null) {
|
||||
add(CoverageLoadRequested(date: state.selectedDate!));
|
||||
}
|
||||
},
|
||||
onError: (String errorKey) => state.copyWith(
|
||||
writeStatus: CoverageWriteStatus.submitFailure,
|
||||
writeErrorMessage: errorKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,3 +38,62 @@ final class CoverageRepostShiftRequested extends CoverageEvent {
|
||||
@override
|
||||
List<Object?> get props => <Object?>[shiftId];
|
||||
}
|
||||
|
||||
/// Event to submit a worker review.
|
||||
final class CoverageSubmitReviewRequested extends CoverageEvent {
|
||||
/// Creates a [CoverageSubmitReviewRequested] event.
|
||||
const CoverageSubmitReviewRequested({
|
||||
required this.staffId,
|
||||
required this.rating,
|
||||
this.assignmentId,
|
||||
this.feedback,
|
||||
this.issueFlags,
|
||||
this.markAsFavorite,
|
||||
});
|
||||
|
||||
/// The worker ID to review.
|
||||
final String staffId;
|
||||
|
||||
/// Rating from 1 to 5.
|
||||
final int rating;
|
||||
|
||||
/// Optional assignment ID for context.
|
||||
final String? assignmentId;
|
||||
|
||||
/// Optional text feedback.
|
||||
final String? feedback;
|
||||
|
||||
/// Optional issue flag labels.
|
||||
final List<String>? issueFlags;
|
||||
|
||||
/// Whether to mark/unmark as favorite.
|
||||
final bool? markAsFavorite;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[
|
||||
staffId,
|
||||
rating,
|
||||
assignmentId,
|
||||
feedback,
|
||||
issueFlags,
|
||||
markAsFavorite,
|
||||
];
|
||||
}
|
||||
|
||||
/// Event to cancel a late worker's assignment.
|
||||
final class CoverageCancelLateWorkerRequested extends CoverageEvent {
|
||||
/// Creates a [CoverageCancelLateWorkerRequested] event.
|
||||
const CoverageCancelLateWorkerRequested({
|
||||
required this.assignmentId,
|
||||
this.reason,
|
||||
});
|
||||
|
||||
/// The assignment ID to cancel.
|
||||
final String assignmentId;
|
||||
|
||||
/// Optional reason for cancellation.
|
||||
final String? reason;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[assignmentId, reason];
|
||||
}
|
||||
|
||||
@@ -16,15 +16,32 @@ enum CoverageStatus {
|
||||
failure,
|
||||
}
|
||||
|
||||
/// Status of a write (review / cancel) operation.
|
||||
enum CoverageWriteStatus {
|
||||
/// No write operation in progress.
|
||||
idle,
|
||||
|
||||
/// A write operation is in progress.
|
||||
submitting,
|
||||
|
||||
/// The write operation succeeded.
|
||||
submitted,
|
||||
|
||||
/// The write operation failed.
|
||||
submitFailure,
|
||||
}
|
||||
|
||||
/// State for the coverage feature.
|
||||
final class CoverageState extends Equatable {
|
||||
/// Creates a [CoverageState].
|
||||
const CoverageState({
|
||||
this.status = CoverageStatus.initial,
|
||||
this.selectedDate,
|
||||
this.shifts = const <CoverageShift>[],
|
||||
this.shifts = const <ShiftWithWorkers>[],
|
||||
this.stats,
|
||||
this.errorMessage,
|
||||
this.writeStatus = CoverageWriteStatus.idle,
|
||||
this.writeErrorMessage,
|
||||
});
|
||||
|
||||
/// The current status of data loading.
|
||||
@@ -33,8 +50,8 @@ final class CoverageState extends Equatable {
|
||||
/// The currently selected date.
|
||||
final DateTime? selectedDate;
|
||||
|
||||
/// The list of shifts for the selected date.
|
||||
final List<CoverageShift> shifts;
|
||||
/// The list of shifts with assigned workers for the selected date.
|
||||
final List<ShiftWithWorkers> shifts;
|
||||
|
||||
/// Coverage statistics for the selected date.
|
||||
final CoverageStats? stats;
|
||||
@@ -42,13 +59,21 @@ final class CoverageState extends Equatable {
|
||||
/// Error message if status is failure.
|
||||
final String? errorMessage;
|
||||
|
||||
/// Status of the current write operation (review or cancel).
|
||||
final CoverageWriteStatus writeStatus;
|
||||
|
||||
/// Error message from a failed write operation.
|
||||
final String? writeErrorMessage;
|
||||
|
||||
/// Creates a copy of this state with the given fields replaced.
|
||||
CoverageState copyWith({
|
||||
CoverageStatus? status,
|
||||
DateTime? selectedDate,
|
||||
List<CoverageShift>? shifts,
|
||||
List<ShiftWithWorkers>? shifts,
|
||||
CoverageStats? stats,
|
||||
String? errorMessage,
|
||||
CoverageWriteStatus? writeStatus,
|
||||
String? writeErrorMessage,
|
||||
}) {
|
||||
return CoverageState(
|
||||
status: status ?? this.status,
|
||||
@@ -56,6 +81,8 @@ final class CoverageState extends Equatable {
|
||||
shifts: shifts ?? this.shifts,
|
||||
stats: stats ?? this.stats,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
writeStatus: writeStatus ?? this.writeStatus,
|
||||
writeErrorMessage: writeErrorMessage ?? this.writeErrorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,5 +93,7 @@ final class CoverageState extends Equatable {
|
||||
shifts,
|
||||
stats,
|
||||
errorMessage,
|
||||
writeStatus,
|
||||
writeErrorMessage,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import '../blocs/coverage_bloc.dart';
|
||||
import '../blocs/coverage_event.dart';
|
||||
import '../blocs/coverage_state.dart';
|
||||
import '../widgets/coverage_calendar_selector.dart';
|
||||
import '../widgets/coverage_page_skeleton.dart';
|
||||
import '../widgets/coverage_quick_stats.dart';
|
||||
import '../widgets/coverage_shift_list.dart';
|
||||
import '../widgets/coverage_stats_header.dart';
|
||||
import '../widgets/late_workers_alert.dart';
|
||||
import 'package:client_coverage/src/presentation/blocs/coverage_bloc.dart';
|
||||
import 'package:client_coverage/src/presentation/blocs/coverage_event.dart';
|
||||
import 'package:client_coverage/src/presentation/blocs/coverage_state.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_calendar_selector.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_page_skeleton.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_quick_stats.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_shift_list.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_stats_header.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/late_workers_alert.dart';
|
||||
|
||||
/// Page for displaying daily coverage information.
|
||||
///
|
||||
@@ -102,7 +102,8 @@ class _CoveragePageState extends State<CoveragePage> {
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(UiConstants.space2),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primaryForeground.withValues(alpha: 0.2),
|
||||
color: UiColors.primaryForeground
|
||||
.withValues(alpha: 0.2),
|
||||
borderRadius: UiConstants.radiusMd,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -147,11 +148,12 @@ class _CoveragePageState extends State<CoveragePage> {
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
CoverageStatsHeader(
|
||||
coveragePercent:
|
||||
(state.stats?.coveragePercent ?? 0)
|
||||
(state.stats?.totalCoveragePercentage ?? 0)
|
||||
.toDouble(),
|
||||
totalConfirmed:
|
||||
state.stats?.totalConfirmed ?? 0,
|
||||
totalNeeded: state.stats?.totalNeeded ?? 0,
|
||||
state.stats?.totalPositionsConfirmed ?? 0,
|
||||
totalNeeded:
|
||||
state.stats?.totalPositionsNeeded ?? 0,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -207,7 +209,8 @@ class _CoveragePageState extends State<CoveragePage> {
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
UiButton.secondary(
|
||||
text: context.t.client_coverage.page.retry,
|
||||
onPressed: () => BlocProvider.of<CoverageBloc>(context).add(
|
||||
onPressed: () =>
|
||||
BlocProvider.of<CoverageBloc>(context).add(
|
||||
const CoverageRefreshRequested(),
|
||||
),
|
||||
),
|
||||
@@ -227,8 +230,11 @@ class _CoveragePageState extends State<CoveragePage> {
|
||||
Column(
|
||||
spacing: UiConstants.space2,
|
||||
children: <Widget>[
|
||||
if (state.stats != null && state.stats!.late > 0) ...<Widget>[
|
||||
LateWorkersAlert(lateCount: state.stats!.late),
|
||||
if (state.stats != null &&
|
||||
state.stats!.totalWorkersLate > 0) ...<Widget>[
|
||||
LateWorkersAlert(
|
||||
lateCount: state.stats!.totalWorkersLate,
|
||||
),
|
||||
],
|
||||
if (state.stats != null) ...<Widget>[
|
||||
CoverageQuickStats(stats: state.stats!),
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'calendar_nav_button.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/calendar_nav_button.dart';
|
||||
|
||||
/// Calendar selector widget for choosing dates.
|
||||
///
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'shift_card_skeleton.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_page_skeleton/shift_card_skeleton.dart';
|
||||
|
||||
/// Shimmer loading skeleton that mimics the coverage page loaded layout.
|
||||
///
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import 'coverage_stat_card.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_stat_card.dart';
|
||||
|
||||
/// Quick statistics cards showing coverage metrics.
|
||||
///
|
||||
@@ -27,7 +27,7 @@ class CoverageQuickStats extends StatelessWidget {
|
||||
child: CoverageStatCard(
|
||||
icon: UiIcons.success,
|
||||
label: context.t.client_coverage.stats.checked_in,
|
||||
value: stats.checkedIn.toString(),
|
||||
value: stats.totalWorkersCheckedIn.toString(),
|
||||
color: UiColors.iconSuccess,
|
||||
),
|
||||
),
|
||||
@@ -35,7 +35,7 @@ class CoverageQuickStats extends StatelessWidget {
|
||||
child: CoverageStatCard(
|
||||
icon: UiIcons.clock,
|
||||
label: context.t.client_coverage.stats.en_route,
|
||||
value: stats.enRoute.toString(),
|
||||
value: stats.totalWorkersEnRoute.toString(),
|
||||
color: UiColors.textWarning,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import 'shift_header.dart';
|
||||
import 'worker_row.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/shift_header.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/worker_row.dart';
|
||||
|
||||
/// List of shifts with their workers.
|
||||
///
|
||||
@@ -18,20 +18,12 @@ class CoverageShiftList extends StatelessWidget {
|
||||
});
|
||||
|
||||
/// The list of shifts to display.
|
||||
final List<CoverageShift> shifts;
|
||||
final List<ShiftWithWorkers> shifts;
|
||||
|
||||
/// Formats a time string (HH:mm) to a readable format (h:mm a).
|
||||
String _formatTime(String? time) {
|
||||
/// Formats a [DateTime] to a readable time string (h:mm a).
|
||||
String _formatTime(DateTime? time) {
|
||||
if (time == null) return '';
|
||||
final List<String> parts = time.split(':');
|
||||
final DateTime dt = DateTime(
|
||||
2022,
|
||||
1,
|
||||
1,
|
||||
int.parse(parts[0]),
|
||||
int.parse(parts[1]),
|
||||
);
|
||||
return DateFormat('h:mm a').format(dt);
|
||||
return DateFormat('h:mm a').format(time);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -65,7 +57,12 @@ class CoverageShiftList extends StatelessWidget {
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: shifts.map((CoverageShift shift) {
|
||||
children: shifts.map((ShiftWithWorkers shift) {
|
||||
final int coveragePercent = shift.requiredWorkerCount > 0
|
||||
? ((shift.assignedWorkerCount / shift.requiredWorkerCount) * 100)
|
||||
.round()
|
||||
: 0;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: UiConstants.space3),
|
||||
decoration: BoxDecoration(
|
||||
@@ -77,29 +74,30 @@ class CoverageShiftList extends StatelessWidget {
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
ShiftHeader(
|
||||
title: shift.title,
|
||||
location: shift.location,
|
||||
startTime: _formatTime(shift.startTime),
|
||||
current: shift.workers.length,
|
||||
total: shift.workersNeeded,
|
||||
coveragePercent: shift.coveragePercent,
|
||||
shiftId: shift.id,
|
||||
title: shift.roleName,
|
||||
location: '', // V2 API does not return location on coverage
|
||||
startTime: _formatTime(shift.timeRange.startsAt),
|
||||
current: shift.assignedWorkerCount,
|
||||
total: shift.requiredWorkerCount,
|
||||
coveragePercent: coveragePercent,
|
||||
shiftId: shift.shiftId,
|
||||
),
|
||||
if (shift.workers.isNotEmpty)
|
||||
if (shift.assignedWorkers.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space3),
|
||||
child: Column(
|
||||
children:
|
||||
shift.workers.map<Widget>((CoverageWorker worker) {
|
||||
final bool isLast = worker == shift.workers.last;
|
||||
children: shift.assignedWorkers
|
||||
.map<Widget>((AssignedWorker worker) {
|
||||
final bool isLast =
|
||||
worker == shift.assignedWorkers.last;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: isLast ? 0 : UiConstants.space2,
|
||||
),
|
||||
child: WorkerRow(
|
||||
worker: worker,
|
||||
shiftStartTime: _formatTime(shift.startTime),
|
||||
formatTime: _formatTime,
|
||||
shiftStartTime:
|
||||
_formatTime(shift.timeRange.startsAt),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'coverage_badge.dart';
|
||||
import 'package:client_coverage/src/presentation/widgets/coverage_badge.dart';
|
||||
|
||||
/// Header section for a shift card showing title, location, time, and coverage.
|
||||
class ShiftHeader extends StatelessWidget {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Row displaying a single worker's avatar, name, status, and badge.
|
||||
@@ -9,18 +10,20 @@ class WorkerRow extends StatelessWidget {
|
||||
const WorkerRow({
|
||||
required this.worker,
|
||||
required this.shiftStartTime,
|
||||
required this.formatTime,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The worker data to display.
|
||||
final CoverageWorker worker;
|
||||
/// The assigned worker data to display.
|
||||
final AssignedWorker worker;
|
||||
|
||||
/// The formatted shift start time.
|
||||
final String shiftStartTime;
|
||||
|
||||
/// Callback to format a raw time string into a readable format.
|
||||
final String Function(String?) formatTime;
|
||||
/// Formats a [DateTime] to a readable time string (h:mm a).
|
||||
String _formatCheckInTime(DateTime? time) {
|
||||
if (time == null) return '';
|
||||
return DateFormat('h:mm a').format(time);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -38,21 +41,21 @@ class WorkerRow extends StatelessWidget {
|
||||
String badgeLabel;
|
||||
|
||||
switch (worker.status) {
|
||||
case CoverageWorkerStatus.checkedIn:
|
||||
case AssignmentStatus.checkedIn:
|
||||
bg = UiColors.textSuccess.withAlpha(26);
|
||||
border = UiColors.textSuccess;
|
||||
textBg = UiColors.textSuccess.withAlpha(51);
|
||||
textColor = UiColors.textSuccess;
|
||||
icon = UiIcons.success;
|
||||
statusText = l10n.status_checked_in_at(
|
||||
time: formatTime(worker.checkInTime),
|
||||
time: _formatCheckInTime(worker.checkInAt),
|
||||
);
|
||||
badgeBg = UiColors.textSuccess.withAlpha(40);
|
||||
badgeText = UiColors.textSuccess;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = l10n.status_on_site;
|
||||
case CoverageWorkerStatus.confirmed:
|
||||
if (worker.checkInTime == null) {
|
||||
case AssignmentStatus.accepted:
|
||||
if (worker.checkInAt == null) {
|
||||
bg = UiColors.textWarning.withAlpha(26);
|
||||
border = UiColors.textWarning;
|
||||
textBg = UiColors.textWarning.withAlpha(51);
|
||||
@@ -75,29 +78,7 @@ class WorkerRow extends StatelessWidget {
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = l10n.status_confirmed;
|
||||
}
|
||||
case CoverageWorkerStatus.late:
|
||||
bg = UiColors.destructive.withAlpha(26);
|
||||
border = UiColors.destructive;
|
||||
textBg = UiColors.destructive.withAlpha(51);
|
||||
textColor = UiColors.destructive;
|
||||
icon = UiIcons.warning;
|
||||
statusText = l10n.status_running_late;
|
||||
badgeBg = UiColors.destructive.withAlpha(40);
|
||||
badgeText = UiColors.destructive;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = l10n.status_late;
|
||||
case CoverageWorkerStatus.checkedOut:
|
||||
bg = UiColors.muted.withAlpha(26);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withAlpha(51);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.success;
|
||||
statusText = l10n.status_checked_out;
|
||||
badgeBg = UiColors.textSecondary.withAlpha(40);
|
||||
badgeText = UiColors.textSecondary;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = l10n.status_done;
|
||||
case CoverageWorkerStatus.noShow:
|
||||
case AssignmentStatus.noShow:
|
||||
bg = UiColors.destructive.withAlpha(26);
|
||||
border = UiColors.destructive;
|
||||
textBg = UiColors.destructive.withAlpha(51);
|
||||
@@ -108,7 +89,18 @@ class WorkerRow extends StatelessWidget {
|
||||
badgeText = UiColors.destructive;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = l10n.status_no_show;
|
||||
case CoverageWorkerStatus.completed:
|
||||
case AssignmentStatus.checkedOut:
|
||||
bg = UiColors.muted.withAlpha(26);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withAlpha(51);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.success;
|
||||
statusText = l10n.status_checked_out;
|
||||
badgeBg = UiColors.textSecondary.withAlpha(40);
|
||||
badgeText = UiColors.textSecondary;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = l10n.status_done;
|
||||
case AssignmentStatus.completed:
|
||||
bg = UiColors.iconSuccess.withAlpha(26);
|
||||
border = UiColors.iconSuccess;
|
||||
textBg = UiColors.iconSuccess.withAlpha(51);
|
||||
@@ -119,20 +111,20 @@ class WorkerRow extends StatelessWidget {
|
||||
badgeText = UiColors.textSuccess;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = l10n.status_completed;
|
||||
case CoverageWorkerStatus.pending:
|
||||
case CoverageWorkerStatus.accepted:
|
||||
case CoverageWorkerStatus.rejected:
|
||||
case AssignmentStatus.assigned:
|
||||
case AssignmentStatus.swapRequested:
|
||||
case AssignmentStatus.cancelled:
|
||||
case AssignmentStatus.unknown:
|
||||
bg = UiColors.muted.withAlpha(26);
|
||||
border = UiColors.border;
|
||||
textBg = UiColors.muted.withAlpha(51);
|
||||
textColor = UiColors.textSecondary;
|
||||
icon = UiIcons.clock;
|
||||
statusText = worker.status.name.toUpperCase();
|
||||
statusText = worker.status.value;
|
||||
badgeBg = UiColors.textSecondary.withAlpha(40);
|
||||
badgeText = UiColors.textSecondary;
|
||||
badgeBorder = badgeText;
|
||||
badgeLabel = worker.status.name[0].toUpperCase() +
|
||||
worker.status.name.substring(1);
|
||||
badgeLabel = worker.status.value;
|
||||
}
|
||||
|
||||
return Container(
|
||||
@@ -156,7 +148,7 @@ class WorkerRow extends StatelessWidget {
|
||||
child: CircleAvatar(
|
||||
backgroundColor: textBg,
|
||||
child: Text(
|
||||
worker.name.isNotEmpty ? worker.name[0] : 'W',
|
||||
worker.fullName.isNotEmpty ? worker.fullName[0] : 'W',
|
||||
style: UiTypography.body1b.copyWith(
|
||||
color: textColor,
|
||||
),
|
||||
@@ -188,7 +180,7 @@ class WorkerRow extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
worker.name,
|
||||
worker.fullName,
|
||||
style: UiTypography.body2b.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
|
||||
@@ -10,7 +10,7 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# Internal packages
|
||||
design_system:
|
||||
path: ../../../design_system
|
||||
@@ -18,17 +18,14 @@ dependencies:
|
||||
path: ../../../domain
|
||||
krow_core:
|
||||
path: ../../../core
|
||||
krow_data_connect:
|
||||
path: ../../../data_connect
|
||||
core_localization:
|
||||
path: ../../../core_localization
|
||||
|
||||
|
||||
# External packages
|
||||
flutter_modular: ^6.3.4
|
||||
flutter_bloc: ^8.1.6
|
||||
equatable: ^2.0.7
|
||||
intl: ^0.20.0
|
||||
firebase_data_connect: ^0.2.2+1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user