Enhance staff profile management by updating data structures and repository implementation

- Added new fields to the Staff entity for better profile details.
- Updated ProfileRepositoryImpl to fetch staff profiles using a connector.
- Refactored GetProfileUseCase to map staff data to UI entities.
- Improved dependency injection in StaffProfileModule.
- Cleaned up unused mock data references and streamlined profile page logic.
This commit is contained in:
Achintha Isuru
2026-01-26 18:03:16 -05:00
parent 43922ab014
commit f4ac292c14
7 changed files with 85 additions and 40 deletions

View File

@@ -29,17 +29,21 @@ enum StaffStatus {
/// Contains all personal and professional details of a staff member. /// Contains all personal and professional details of a staff member.
/// Linked to a [User] via [authProviderId]. /// Linked to a [User] via [authProviderId].
class Staff extends Equatable { class Staff extends Equatable {
const Staff({ const Staff({
required this.id, required this.id,
required this.authProviderId, required this.authProviderId,
required this.name, required this.name,
required this.email, required this.email,
this.phone, this.phone,
this.avatar,
required this.status, required this.status,
this.address, this.address,
this.avatar, this.totalShifts,
this.livePhoto, this.averageRating,
this.onTimeRate,
this.noShowCount,
this.cancellationCount,
this.reliabilityScore,
}); });
/// Unique identifier for the staff profile. /// Unique identifier for the staff profile.
final String id; final String id;
@@ -56,17 +60,34 @@ class Staff extends Equatable {
/// Contact phone number. /// Contact phone number.
final String? phone; final String? phone;
/// Profile picture URL.
final String? avatar;
/// Current workflow status of the staff member. /// Current workflow status of the staff member.
final StaffStatus status; final StaffStatus status;
/// Physical address string. /// The user's physical address.
///
/// Can be used for location-based job matching.
final String? address; final String? address;
/// URL to the avatar image. /// The total number of shifts completed.
final String? avatar; final int? totalShifts;
/// URL to a verified live photo for identity verification. /// The average rating from businesses.
final String? livePhoto; final double? averageRating;
/// The percentage of shifts arrived on time.
final int? onTimeRate;
/// The number of no-shows.
final int? noShowCount;
/// The number of cancellations within 24h.
final int? cancellationCount;
/// The reliability score (0-100).
final int? reliabilityScore;
@override @override
List<Object?> get props => <Object?>[ List<Object?> get props => <Object?>[
@@ -75,9 +96,14 @@ class Staff extends Equatable {
name, name,
email, email,
phone, phone,
avatar,
status, status,
address, address,
avatar, totalShifts,
livePhoto, averageRating,
onTimeRate,
noShowCount,
cancellationCount,
reliabilityScore,
]; ];
} }

View File

@@ -41,7 +41,7 @@ class ClientCreateOrderModule extends Module {
i.addLazySingleton(CreateRapidOrderUseCase.new); i.addLazySingleton(CreateRapidOrderUseCase.new);
// BLoCs // BLoCs
i.addSingleton<ClientCreateOrderBloc>(ClientCreateOrderBloc.new); i.add<ClientCreateOrderBloc>(ClientCreateOrderBloc.new);
i.add<RapidOrderBloc>(RapidOrderBloc.new); i.add<RapidOrderBloc>(RapidOrderBloc.new);
i.add<OneTimeOrderBloc>( i.add<OneTimeOrderBloc>(
() => OneTimeOrderBloc( () => OneTimeOrderBloc(

View File

@@ -14,22 +14,49 @@ import '../../domain/repositories/profile_repository.dart';
/// Currently uses [ProfileRepositoryMock] from data_connect. /// Currently uses [ProfileRepositoryMock] from data_connect.
/// When Firebase Data Connect is ready, this will be swapped with a real implementation. /// When Firebase Data Connect is ready, this will be swapped with a real implementation.
class ProfileRepositoryImpl implements ProfileRepositoryInterface { class ProfileRepositoryImpl implements ProfileRepositoryInterface {
final ProfileRepositoryMock _dataConnectRepository;
/// Creates a [ProfileRepositoryImpl]. /// Creates a [ProfileRepositoryImpl].
/// ///
/// Requires a [ProfileRepositoryMock] from the data_connect package. /// Requires a [ExampleConnector] from the data_connect package.
const ProfileRepositoryImpl(this._dataConnectRepository); const ProfileRepositoryImpl({required this.connector});
/// The Data Connect connector used for data operations.
final ExampleConnector connector;
@override @override
Future<Staff> getStaffProfile(String userId) { Future<Staff> getStaffProfile(String userId) async {
// Delegate directly to data_connect - no business logic here // ignore: always_specify_types
return _dataConnectRepository.getStaffProfile(userId); final response = await connector.getStaffByUserId(userId: userId).execute();
if (response.data.staffs.isEmpty) {
// TODO: Handle user not found properly with domain exception
throw Exception('Staff not found');
}
final GetStaffByUserIdStaffs rawStaff = response.data.staffs.first;
// Map the raw data connect object to the Domain Entity
return Staff(
id: rawStaff.id,
authProviderId: rawStaff.userId,
name: rawStaff.fullName,
email: rawStaff.email ?? '',
phone: rawStaff.phone,
avatar: rawStaff.photoUrl,
status: StaffStatus.active,
address: null,
totalShifts: rawStaff.totalShifts,
averageRating: rawStaff.averageRating,
onTimeRate: rawStaff.onTimeRate,
noShowCount: rawStaff.noShowCount,
cancellationCount: rawStaff.cancellationCount,
reliabilityScore: rawStaff.reliabilityScore,
);
} }
@override @override
Future<void> signOut() { Future<void> signOut() {
// Delegate directly to data_connect - no business logic here // TODO: Implement sign out via Auth interface, not profile repository
return _dataConnectRepository.signOut(); // For now, no-op or delegate if connector has auth
return Future.value();
} }
} }

View File

@@ -31,19 +31,18 @@ class GetProfileUseCase implements UseCase<String, StaffProfileUI> {
final staff = await _repository.getStaffProfile(userId); final staff = await _repository.getStaffProfile(userId);
// Map to UI entity with additional profile data // Map to UI entity with additional profile data
// TODO: Replace mock data with actual profile statistics from backend
return StaffProfileUI( return StaffProfileUI(
staff: staff, staff: staff,
totalShifts: 0, totalShifts: staff.totalShifts ?? 0,
averageRating: 5.0, averageRating: staff.averageRating ?? 5.0,
onTimeRate: 100, onTimeRate: staff.onTimeRate ?? 0,
noShowCount: 0, noShowCount: staff.noShowCount ?? 0,
cancellationCount: 0, cancellationCount: staff.cancellationCount ?? 0,
reliabilityScore: 100, reliabilityScore: staff.reliabilityScore ?? 100,
hasPersonalInfo: staff.phone != null && staff.phone!.isNotEmpty, hasPersonalInfo: staff.phone != null && staff.phone!.isNotEmpty,
hasEmergencyContact: false, // TODO: Fetch from backend hasEmergencyContact: false, // TODO: Fetch from backend
hasExperience: false, // TODO: Fetch from backend hasExperience: false, // TODO: Fetch from backend
hasAttire: staff.avatar != null, hasAttire: false, // TODO: Check attire items from backend when available
hasDocuments: false, // TODO: Fetch from backend hasDocuments: false, // TODO: Fetch from backend
hasCertificates: false, // TODO: Fetch from backend hasCertificates: false, // TODO: Fetch from backend
hasTaxForms: false, // TODO: Fetch from backend hasTaxForms: false, // TODO: Fetch from backend

View File

@@ -28,8 +28,8 @@ class StaffProfilePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final i18n = t.staff.profile; final TranslationsStaffProfileEn i18n = t.staff.profile;
final cubit = Modular.get<ProfileCubit>(); final ProfileCubit cubit = Modular.get<ProfileCubit>();
// Load profile data on first build // Load profile data on first build
// TODO: Get actual userId from auth session // TODO: Get actual userId from auth session
@@ -40,7 +40,6 @@ class StaffProfilePage extends StatelessWidget {
} }
return Scaffold( return Scaffold(
backgroundColor: UiColors.background,
body: BlocBuilder<ProfileCubit, ProfileState>( body: BlocBuilder<ProfileCubit, ProfileState>(
bloc: cubit, bloc: cubit,
builder: (context, state) { builder: (context, state) {

View File

@@ -15,19 +15,15 @@ import 'presentation/pages/staff_profile_page.dart';
/// following Clean Architecture principles. /// following Clean Architecture principles.
/// ///
/// Dependency flow: /// Dependency flow:
/// - Data source (ProfileRepositoryMock) from data_connect package
/// - Repository implementation (ProfileRepositoryImpl) delegates to data_connect /// - Repository implementation (ProfileRepositoryImpl) delegates to data_connect
/// - Use cases depend on repository interface /// - Use cases depend on repository interface
/// - Cubit depends on use cases /// - Cubit depends on use cases
class StaffProfileModule extends Module { class StaffProfileModule extends Module {
@override @override
void binds(Injector i) { void binds(Injector i) {
// Data layer - Get mock from data_connect package
i.addLazySingleton<ProfileRepositoryMock>(ProfileRepositoryMock.new);
// Repository implementation - delegates to data_connect // Repository implementation - delegates to data_connect
i.addLazySingleton<ProfileRepositoryInterface>( i.addLazySingleton<ProfileRepositoryInterface>(
() => ProfileRepositoryImpl(i.get<ProfileRepositoryMock>()), () => ProfileRepositoryImpl(connector: ExampleConnector.instance),
); );
// Use cases - depend on repository interface // Use cases - depend on repository interface
@@ -39,8 +35,7 @@ class StaffProfileModule extends Module {
); );
// Presentation layer - Cubit depends on use cases // Presentation layer - Cubit depends on use cases
// Use addLazySingleton to create a new instance per module lifecycle i.add<ProfileCubit>(
i.addLazySingleton(
() => ProfileCubit( () => ProfileCubit(
i.get<GetProfileUseCase>(), i.get<GetProfileUseCase>(),
i.get<SignOutUseCase>(), i.get<SignOutUseCase>(),

View File

@@ -1,2 +1 @@
/// Export the modular feature definition.
export 'src/staff_profile_module.dart'; export 'src/staff_profile_module.dart';