Refactor staff profile management to remove livePhoto references and update repository methods for current user authentication
This commit is contained in:
@@ -28,7 +28,6 @@ class ProfileRepositoryMock {
|
|||||||
phone: '555-123-4567',
|
phone: '555-123-4567',
|
||||||
status: StaffStatus.active,
|
status: StaffStatus.active,
|
||||||
avatar: null,
|
avatar: null,
|
||||||
livePhoto: null,
|
|
||||||
address: null,
|
address: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,6 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
|||||||
status: domain.StaffStatus.completedProfile,
|
status: domain.StaffStatus.completedProfile,
|
||||||
address: staffRecord.addres,
|
address: staffRecord.addres,
|
||||||
avatar: staffRecord.photoUrl,
|
avatar: staffRecord.photoUrl,
|
||||||
livePhoto: null,
|
|
||||||
);
|
);
|
||||||
StaffSessionStore.instance.setSession(
|
StaffSessionStore.instance.setSession(
|
||||||
StaffSession(user: domainUser, staff: domainStaff),
|
StaffSession(user: domainUser, staff: domainStaff),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
@@ -16,16 +17,26 @@ import '../../domain/repositories/profile_repository.dart';
|
|||||||
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
||||||
/// Creates a [ProfileRepositoryImpl].
|
/// Creates a [ProfileRepositoryImpl].
|
||||||
///
|
///
|
||||||
/// Requires a [ExampleConnector] from the data_connect package.
|
/// Requires a [ExampleConnector] from the data_connect package and [FirebaseAuth].
|
||||||
const ProfileRepositoryImpl({required this.connector});
|
const ProfileRepositoryImpl({
|
||||||
|
required this.connector,
|
||||||
|
required this.firebaseAuth,
|
||||||
|
});
|
||||||
|
|
||||||
/// The Data Connect connector used for data operations.
|
/// The Data Connect connector used for data operations.
|
||||||
final ExampleConnector connector;
|
final ExampleConnector connector;
|
||||||
|
|
||||||
|
/// The Firebase Auth instance.
|
||||||
|
final FirebaseAuth firebaseAuth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Staff> getStaffProfile(String userId) async {
|
Future<Staff> getStaffProfile() async {
|
||||||
// ignore: always_specify_types
|
final user = firebaseAuth.currentUser;
|
||||||
final response = await connector.getStaffByUserId(userId: userId).execute();
|
if (user == null) {
|
||||||
|
throw Exception('User not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await connector.getStaffByUserId(userId: user.uid).execute();
|
||||||
|
|
||||||
if (response.data.staffs.isEmpty) {
|
if (response.data.staffs.isEmpty) {
|
||||||
// TODO: Handle user not found properly with domain exception
|
// TODO: Handle user not found properly with domain exception
|
||||||
@@ -43,7 +54,7 @@ class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
|||||||
phone: rawStaff.phone,
|
phone: rawStaff.phone,
|
||||||
avatar: rawStaff.photoUrl,
|
avatar: rawStaff.photoUrl,
|
||||||
status: StaffStatus.active,
|
status: StaffStatus.active,
|
||||||
address: null,
|
address: rawStaff.addres,
|
||||||
totalShifts: rawStaff.totalShifts,
|
totalShifts: rawStaff.totalShifts,
|
||||||
averageRating: rawStaff.averageRating,
|
averageRating: rawStaff.averageRating,
|
||||||
onTimeRate: rawStaff.onTimeRate,
|
onTimeRate: rawStaff.onTimeRate,
|
||||||
@@ -54,9 +65,11 @@ class ProfileRepositoryImpl implements ProfileRepositoryInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> signOut() {
|
Future<void> signOut() async {
|
||||||
// TODO: Implement sign out via Auth interface, not profile repository
|
try {
|
||||||
// For now, no-op or delegate if connector has auth
|
await firebaseAuth.signOut();
|
||||||
return Future.value();
|
} catch (e) {
|
||||||
|
throw Exception('Error signing out: ${e.toString()}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
/// - Defines business requirements without implementation details
|
/// - Defines business requirements without implementation details
|
||||||
/// - Allows the domain layer to be independent of data sources
|
/// - Allows the domain layer to be independent of data sources
|
||||||
abstract interface class ProfileRepositoryInterface {
|
abstract interface class ProfileRepositoryInterface {
|
||||||
/// Fetches the staff profile for the given user ID.
|
/// Fetches the staff profile for the current authenticated user.
|
||||||
///
|
///
|
||||||
/// Returns a [Staff] entity from the shared domain package containing
|
/// Returns a [Staff] entity from the shared domain package containing
|
||||||
/// all profile information.
|
/// all profile information.
|
||||||
///
|
///
|
||||||
/// Throws an exception if the profile cannot be retrieved.
|
/// Throws an exception if the profile cannot be retrieved.
|
||||||
Future<Staff> getStaffProfile(String userId);
|
Future<Staff> getStaffProfile();
|
||||||
|
|
||||||
/// Signs out the current user.
|
/// Signs out the current user.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
|
||||||
|
|
||||||
/// UI entity representing extended profile information for a staff member.
|
|
||||||
///
|
|
||||||
/// This UI entity wraps the shared [Staff] domain entity and adds
|
|
||||||
/// presentation-layer specific data such as:
|
|
||||||
/// - Reliability statistics (shifts, ratings, etc.)
|
|
||||||
/// - Profile completion status
|
|
||||||
/// - Performance metrics
|
|
||||||
///
|
|
||||||
/// Following Clean Architecture principles, this entity:
|
|
||||||
/// - Lives in the feature's domain/ui_entities layer
|
|
||||||
/// - Is used only by the presentation layer
|
|
||||||
/// - Extends the core [Staff] entity with UI-specific data
|
|
||||||
/// - Does NOT replace domain entities in repositories or use cases
|
|
||||||
class StaffProfileUI extends Equatable {
|
|
||||||
/// The underlying staff entity from the shared domain
|
|
||||||
final Staff staff;
|
|
||||||
|
|
||||||
/// Total number of shifts worked
|
|
||||||
final int totalShifts;
|
|
||||||
|
|
||||||
/// Average rating received from clients (0.0 - 5.0)
|
|
||||||
final double averageRating;
|
|
||||||
|
|
||||||
/// Percentage of shifts where staff arrived on time
|
|
||||||
final int onTimeRate;
|
|
||||||
|
|
||||||
/// Number of times the staff failed to show up for a shift
|
|
||||||
final int noShowCount;
|
|
||||||
|
|
||||||
/// Number of shifts the staff has cancelled
|
|
||||||
final int cancellationCount;
|
|
||||||
|
|
||||||
/// Overall reliability score (0-100)
|
|
||||||
final int reliabilityScore;
|
|
||||||
|
|
||||||
/// Whether personal information section is complete
|
|
||||||
final bool hasPersonalInfo;
|
|
||||||
|
|
||||||
/// Whether emergency contact section is complete
|
|
||||||
final bool hasEmergencyContact;
|
|
||||||
|
|
||||||
/// Whether work experience section is complete
|
|
||||||
final bool hasExperience;
|
|
||||||
|
|
||||||
/// Whether attire photo has been uploaded
|
|
||||||
final bool hasAttire;
|
|
||||||
|
|
||||||
/// Whether required documents have been uploaded
|
|
||||||
final bool hasDocuments;
|
|
||||||
|
|
||||||
/// Whether certificates have been uploaded
|
|
||||||
final bool hasCertificates;
|
|
||||||
|
|
||||||
/// Whether tax forms have been submitted
|
|
||||||
final bool hasTaxForms;
|
|
||||||
|
|
||||||
const StaffProfileUI({
|
|
||||||
required this.staff,
|
|
||||||
required this.totalShifts,
|
|
||||||
required this.averageRating,
|
|
||||||
required this.onTimeRate,
|
|
||||||
required this.noShowCount,
|
|
||||||
required this.cancellationCount,
|
|
||||||
required this.reliabilityScore,
|
|
||||||
required this.hasPersonalInfo,
|
|
||||||
required this.hasEmergencyContact,
|
|
||||||
required this.hasExperience,
|
|
||||||
required this.hasAttire,
|
|
||||||
required this.hasDocuments,
|
|
||||||
required this.hasCertificates,
|
|
||||||
required this.hasTaxForms,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Convenience getters that delegate to the underlying Staff entity
|
|
||||||
String get fullName => staff.name;
|
|
||||||
String get email => staff.email;
|
|
||||||
String? get photoUrl => staff.avatar;
|
|
||||||
String get userId => staff.authProviderId;
|
|
||||||
String get staffId => staff.id;
|
|
||||||
|
|
||||||
/// Maps staff status to a level string for display
|
|
||||||
/// TODO: Replace with actual level data when available in Staff entity
|
|
||||||
String get level => _mapStatusToLevel(staff.status);
|
|
||||||
|
|
||||||
String _mapStatusToLevel(StaffStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case StaffStatus.active:
|
|
||||||
case StaffStatus.verified:
|
|
||||||
return 'Krower I';
|
|
||||||
case StaffStatus.pending:
|
|
||||||
case StaffStatus.completedProfile:
|
|
||||||
return 'Pending';
|
|
||||||
default:
|
|
||||||
return 'New';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
staff,
|
|
||||||
totalShifts,
|
|
||||||
averageRating,
|
|
||||||
onTimeRate,
|
|
||||||
noShowCount,
|
|
||||||
cancellationCount,
|
|
||||||
reliabilityScore,
|
|
||||||
hasPersonalInfo,
|
|
||||||
hasEmergencyContact,
|
|
||||||
hasExperience,
|
|
||||||
hasAttire,
|
|
||||||
hasDocuments,
|
|
||||||
hasCertificates,
|
|
||||||
hasTaxForms,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -2,22 +2,14 @@ import 'package:krow_core/core.dart';
|
|||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../repositories/profile_repository.dart';
|
import '../repositories/profile_repository.dart';
|
||||||
import '../ui_entities/staff_profile_ui.dart';
|
|
||||||
|
|
||||||
/// Use case for fetching a staff member's extended profile information.
|
/// Use case for fetching a staff member's extended profile information.
|
||||||
///
|
///
|
||||||
/// This use case:
|
/// This use case:
|
||||||
/// 1. Fetches the core [Staff] entity from the repository
|
/// 1. Fetches the [Staff] object from the repository
|
||||||
/// 2. Maps it to a [StaffProfileUI] entity with additional UI-specific data
|
/// 2. Returns it directly to the presentation layer
|
||||||
///
|
///
|
||||||
/// Following Clean Architecture principles:
|
class GetProfileUseCase implements UseCase<void, Staff> {
|
||||||
/// - Depends only on the repository interface (dependency inversion)
|
|
||||||
/// - Returns a UI entity suitable for the presentation layer
|
|
||||||
/// - Encapsulates the mapping logic from domain to UI entities
|
|
||||||
///
|
|
||||||
/// TODO: When profile statistics API is available, fetch and map real data
|
|
||||||
/// Currently returns mock statistics data.
|
|
||||||
class GetProfileUseCase implements UseCase<String, StaffProfileUI> {
|
|
||||||
final ProfileRepositoryInterface _repository;
|
final ProfileRepositoryInterface _repository;
|
||||||
|
|
||||||
/// Creates a [GetProfileUseCase].
|
/// Creates a [GetProfileUseCase].
|
||||||
@@ -26,26 +18,8 @@ class GetProfileUseCase implements UseCase<String, StaffProfileUI> {
|
|||||||
const GetProfileUseCase(this._repository);
|
const GetProfileUseCase(this._repository);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<StaffProfileUI> call(String userId) async {
|
Future<Staff> call([void params]) async {
|
||||||
// Fetch core Staff entity from repository
|
// Fetch staff object from repository and return directly
|
||||||
final staff = await _repository.getStaffProfile(userId);
|
return await _repository.getStaffProfile();
|
||||||
|
|
||||||
// Map to UI entity with additional profile data
|
|
||||||
return StaffProfileUI(
|
|
||||||
staff: staff,
|
|
||||||
totalShifts: staff.totalShifts ?? 0,
|
|
||||||
averageRating: staff.averageRating ?? 5.0,
|
|
||||||
onTimeRate: staff.onTimeRate ?? 0,
|
|
||||||
noShowCount: staff.noShowCount ?? 0,
|
|
||||||
cancellationCount: staff.cancellationCount ?? 0,
|
|
||||||
reliabilityScore: staff.reliabilityScore ?? 100,
|
|
||||||
hasPersonalInfo: staff.phone != null && staff.phone!.isNotEmpty,
|
|
||||||
hasEmergencyContact: false, // TODO: Fetch from backend
|
|
||||||
hasExperience: false, // TODO: Fetch from backend
|
|
||||||
hasAttire: false, // TODO: Check attire items from backend when available
|
|
||||||
hasDocuments: false, // TODO: Fetch from backend
|
|
||||||
hasCertificates: false, // TODO: Fetch from backend
|
|
||||||
hasTaxForms: false, // TODO: Fetch from backend
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,11 @@ class ProfileCubit extends Cubit<ProfileState> {
|
|||||||
/// Emits [ProfileStatus.loading] while fetching data,
|
/// Emits [ProfileStatus.loading] while fetching data,
|
||||||
/// then [ProfileStatus.loaded] with the profile data on success,
|
/// then [ProfileStatus.loaded] with the profile data on success,
|
||||||
/// or [ProfileStatus.error] if an error occurs.
|
/// or [ProfileStatus.error] if an error occurs.
|
||||||
///
|
Future<void> loadProfile() async {
|
||||||
/// Requires [userId] to identify which profile to load.
|
|
||||||
Future<void> loadProfile(String userId) async {
|
|
||||||
emit(state.copyWith(status: ProfileStatus.loading));
|
emit(state.copyWith(status: ProfileStatus.loading));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final profile = await _getProfileUseCase(userId);
|
final profile = await _getProfileUseCase();
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
status: ProfileStatus.loaded,
|
status: ProfileStatus.loaded,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
@@ -45,8 +43,15 @@ class ProfileCubit extends Cubit<ProfileState> {
|
|||||||
/// Delegates to the sign-out use case which handles session cleanup
|
/// Delegates to the sign-out use case which handles session cleanup
|
||||||
/// and navigation.
|
/// and navigation.
|
||||||
Future<void> signOut() async {
|
Future<void> signOut() async {
|
||||||
|
if (state.status == ProfileStatus.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(state.copyWith(status: ProfileStatus.loading));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _signOutUseCase();
|
await _signOutUseCase();
|
||||||
|
emit(state.copyWith(status: ProfileStatus.signedOut));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Error handling can be added here if needed
|
// Error handling can be added here if needed
|
||||||
// For now, we let the navigation happen regardless
|
// For now, we let the navigation happen regardless
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
import '../../domain/ui_entities/staff_profile_ui.dart';
|
|
||||||
|
|
||||||
/// Represents the various states of the profile feature.
|
/// Represents the various states of the profile feature.
|
||||||
enum ProfileStatus {
|
enum ProfileStatus {
|
||||||
@@ -13,6 +12,9 @@ enum ProfileStatus {
|
|||||||
/// Profile data loaded successfully
|
/// Profile data loaded successfully
|
||||||
loaded,
|
loaded,
|
||||||
|
|
||||||
|
/// User successfully signed out
|
||||||
|
signedOut,
|
||||||
|
|
||||||
/// An error occurred while loading profile data
|
/// An error occurred while loading profile data
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
@@ -20,14 +22,13 @@ enum ProfileStatus {
|
|||||||
/// State class for the Profile feature.
|
/// State class for the Profile feature.
|
||||||
///
|
///
|
||||||
/// Contains the current profile data and loading status.
|
/// Contains the current profile data and loading status.
|
||||||
/// Uses the [StaffProfileUI] entity which wraps the shared Staff entity
|
/// Uses the [Staff] entity directly from domain layer.
|
||||||
/// with presentation-layer specific data.
|
|
||||||
class ProfileState extends Equatable {
|
class ProfileState extends Equatable {
|
||||||
/// Current status of the profile feature
|
/// Current status of the profile feature
|
||||||
final ProfileStatus status;
|
final ProfileStatus status;
|
||||||
|
|
||||||
/// The staff member's profile UI entity (null if not loaded)
|
/// The staff member's profile object (null if not loaded)
|
||||||
final StaffProfileUI? profile;
|
final Staff? profile;
|
||||||
|
|
||||||
/// Error message if status is error
|
/// Error message if status is error
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
@@ -41,7 +42,7 @@ class ProfileState extends Equatable {
|
|||||||
/// Creates a copy of this state with updated values.
|
/// Creates a copy of this state with updated values.
|
||||||
ProfileState copyWith({
|
ProfileState copyWith({
|
||||||
ProfileStatus? status,
|
ProfileStatus? status,
|
||||||
StaffProfileUI? profile,
|
Staff? profile,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
return ProfileState(
|
return ProfileState(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart' hide ReadContext;
|
|||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:core_localization/core_localization.dart';
|
import 'package:core_localization/core_localization.dart';
|
||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../blocs/profile_cubit.dart';
|
import '../blocs/profile_cubit.dart';
|
||||||
import '../blocs/profile_state.dart';
|
import '../blocs/profile_state.dart';
|
||||||
@@ -26,28 +27,50 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
/// Creates a [StaffProfilePage].
|
/// Creates a [StaffProfilePage].
|
||||||
const StaffProfilePage({super.key});
|
const StaffProfilePage({super.key});
|
||||||
|
|
||||||
|
String _mapStatusToLevel(StaffStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case StaffStatus.active:
|
||||||
|
case StaffStatus.verified:
|
||||||
|
return 'Krower I';
|
||||||
|
case StaffStatus.pending:
|
||||||
|
case StaffStatus.completedProfile:
|
||||||
|
return 'Pending';
|
||||||
|
default:
|
||||||
|
return 'New';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSignOut(ProfileCubit cubit, ProfileState state) {
|
||||||
|
if (state.status != ProfileStatus.loading) {
|
||||||
|
cubit.signOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final TranslationsStaffProfileEn i18n = t.staff.profile;
|
final TranslationsStaffProfileEn i18n = t.staff.profile;
|
||||||
final ProfileCubit 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
|
|
||||||
// For now, using mock userId that matches ProfileRepositoryMock data
|
|
||||||
const userId = 't8P3fYh4y1cPoZbbVPXUhfQCsDo3';
|
|
||||||
if (cubit.state.status == ProfileStatus.initial) {
|
if (cubit.state.status == ProfileStatus.initial) {
|
||||||
cubit.loadProfile(userId);
|
cubit.loadProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: BlocBuilder<ProfileCubit, ProfileState>(
|
body: BlocConsumer<ProfileCubit, ProfileState>(
|
||||||
bloc: cubit,
|
bloc: cubit,
|
||||||
builder: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.status == ProfileStatus.loading) {
|
if (state.status == ProfileStatus.signedOut) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
Modular.to.navigateToGetStarted();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
// Show loading spinner if status is loading
|
||||||
|
if (state.status == ProfileStatus.loading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
if (state.status == ProfileStatus.error) {
|
if (state.status == ProfileStatus.error) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
state.errorMessage ?? 'An error occurred',
|
state.errorMessage ?? 'An error occurred',
|
||||||
@@ -68,13 +91,10 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ProfileHeader(
|
ProfileHeader(
|
||||||
fullName: profile.fullName,
|
fullName: profile.name,
|
||||||
level: profile.level,
|
level: _mapStatusToLevel(profile.status),
|
||||||
photoUrl: profile.photoUrl,
|
photoUrl: profile.avatar,
|
||||||
onSignOutTap: () {
|
onSignOutTap: () => _onSignOut(cubit, state),
|
||||||
context.read<ProfileCubit>().signOut();
|
|
||||||
Modular.to.navigateToGetStarted();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Transform.translate(
|
Transform.translate(
|
||||||
offset: const Offset(0, -24),
|
offset: const Offset(0, -24),
|
||||||
@@ -85,42 +105,44 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ReliabilityStatsCard(
|
ReliabilityStatsCard(
|
||||||
totalShifts: profile.totalShifts,
|
totalShifts: profile.totalShifts ?? 0,
|
||||||
averageRating: profile.averageRating,
|
averageRating: profile.averageRating ?? 0.0,
|
||||||
onTimeRate: profile.onTimeRate,
|
onTimeRate: profile.onTimeRate ?? 0,
|
||||||
noShowCount: profile.noShowCount,
|
noShowCount: profile.noShowCount ?? 0,
|
||||||
cancellationCount: profile.cancellationCount,
|
cancellationCount: profile.cancellationCount ?? 0,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
ReliabilityScoreBar(
|
ReliabilityScoreBar(
|
||||||
reliabilityScore: profile.reliabilityScore,
|
reliabilityScore: profile.reliabilityScore ?? 100,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
SectionTitle(i18n.sections.onboarding),
|
SectionTitle(i18n.sections.onboarding),
|
||||||
ProfileMenuGrid(
|
ProfileMenuGrid(
|
||||||
|
crossAxisCount: 3,
|
||||||
|
|
||||||
children: [
|
children: [
|
||||||
ProfileMenuItem(
|
ProfileMenuItem(
|
||||||
icon: UiIcons.user,
|
icon: UiIcons.user,
|
||||||
label: i18n.menu_items.personal_info,
|
label: i18n.menu_items.personal_info,
|
||||||
completed: profile.hasPersonalInfo,
|
completed: profile.phone != null,
|
||||||
onTap: () => Modular.to.pushPersonalInfo(),
|
onTap: () => Modular.to.pushPersonalInfo(),
|
||||||
),
|
),
|
||||||
ProfileMenuItem(
|
ProfileMenuItem(
|
||||||
icon: UiIcons.phone,
|
icon: UiIcons.phone,
|
||||||
label: i18n.menu_items.emergency_contact,
|
label: i18n.menu_items.emergency_contact,
|
||||||
completed: profile.hasEmergencyContact,
|
completed: false,
|
||||||
onTap: () => Modular.to.pushEmergencyContact(),
|
onTap: () => Modular.to.pushEmergencyContact(),
|
||||||
),
|
),
|
||||||
ProfileMenuItem(
|
ProfileMenuItem(
|
||||||
icon: UiIcons.briefcase,
|
icon: UiIcons.briefcase,
|
||||||
label: i18n.menu_items.experience,
|
label: i18n.menu_items.experience,
|
||||||
completed: profile.hasExperience,
|
completed: false,
|
||||||
onTap: () => Modular.to.pushExperience(),
|
onTap: () => Modular.to.pushExperience(),
|
||||||
),
|
),
|
||||||
ProfileMenuItem(
|
ProfileMenuItem(
|
||||||
icon: UiIcons.user,
|
icon: UiIcons.user,
|
||||||
label: i18n.menu_items.attire,
|
label: i18n.menu_items.attire,
|
||||||
completed: profile.hasAttire,
|
completed: false,
|
||||||
onTap: () => Modular.to.pushAttire(),
|
onTap: () => Modular.to.pushAttire(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -133,19 +155,19 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
ProfileMenuItem(
|
ProfileMenuItem(
|
||||||
icon: UiIcons.file,
|
icon: UiIcons.file,
|
||||||
label: i18n.menu_items.documents,
|
label: i18n.menu_items.documents,
|
||||||
completed: profile.hasDocuments,
|
completed: false,
|
||||||
onTap: () => Modular.to.pushDocuments(),
|
onTap: () => Modular.to.pushDocuments(),
|
||||||
),
|
),
|
||||||
ProfileMenuItem(
|
ProfileMenuItem(
|
||||||
icon: UiIcons.shield,
|
icon: UiIcons.shield,
|
||||||
label: i18n.menu_items.certificates,
|
label: i18n.menu_items.certificates,
|
||||||
completed: profile.hasCertificates,
|
completed: false,
|
||||||
onTap: () => Modular.to.pushCertificates(),
|
onTap: () => Modular.to.pushCertificates(),
|
||||||
),
|
),
|
||||||
ProfileMenuItem(
|
ProfileMenuItem(
|
||||||
icon: UiIcons.file,
|
icon: UiIcons.file,
|
||||||
label: i18n.menu_items.tax_forms,
|
label: i18n.menu_items.tax_forms,
|
||||||
completed: profile.hasTaxForms,
|
completed: false,
|
||||||
onTap: () => Modular.to.pushTaxForms(),
|
onTap: () => Modular.to.pushTaxForms(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -218,10 +240,7 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space6),
|
const SizedBox(height: UiConstants.space6),
|
||||||
LogoutButton(
|
LogoutButton(
|
||||||
onTap: () {
|
onTap: () => _onSignOut(cubit, state),
|
||||||
context.read<ProfileCubit>().signOut();
|
|
||||||
Modular.to.navigateToGetStarted();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
|
||||||
import 'data/repositories/profile_repository_impl.dart';
|
import 'data/repositories/profile_repository_impl.dart';
|
||||||
import 'domain/repositories/profile_repository.dart';
|
import 'domain/repositories/profile_repository.dart';
|
||||||
@@ -23,7 +24,10 @@ class StaffProfileModule extends Module {
|
|||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
// Repository implementation - delegates to data_connect
|
// Repository implementation - delegates to data_connect
|
||||||
i.addLazySingleton<ProfileRepositoryInterface>(
|
i.addLazySingleton<ProfileRepositoryInterface>(
|
||||||
() => ProfileRepositoryImpl(connector: ExampleConnector.instance),
|
() => ProfileRepositoryImpl(
|
||||||
|
connector: ExampleConnector.instance,
|
||||||
|
firebaseAuth: FirebaseAuth.instance,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use cases - depend on repository interface
|
// Use cases - depend on repository interface
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ dependencies:
|
|||||||
path: ../profile_sections/onboarding/emergency_contact
|
path: ../profile_sections/onboarding/emergency_contact
|
||||||
staff_profile_experience:
|
staff_profile_experience:
|
||||||
path: ../profile_sections/onboarding/experience
|
path: ../profile_sections/onboarding/experience
|
||||||
|
firebase_auth: ^6.1.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
|
|||||||
status: StaffStatus.active, // TODO: Map from actual status field when available
|
status: StaffStatus.active, // TODO: Map from actual status field when available
|
||||||
address: dto.addres,
|
address: dto.addres,
|
||||||
avatar: dto.photoUrl,
|
avatar: dto.photoUrl,
|
||||||
livePhoto: null, // TODO: Map when available in data schema
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class PersonalInfoRepositoryMock implements PersonalInfoRepositoryInterface {
|
|||||||
status: StaffStatus.active,
|
status: StaffStatus.active,
|
||||||
address: 'Montreal, Quebec',
|
address: 'Montreal, Quebec',
|
||||||
avatar: null,
|
avatar: null,
|
||||||
livePhoto: null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
|
|||||||
status: staff.status,
|
status: staff.status,
|
||||||
address: staff.address,
|
address: staff.address,
|
||||||
avatar: staff.avatar,
|
avatar: staff.avatar,
|
||||||
livePhoto: staff.livePhoto,
|
|
||||||
);
|
);
|
||||||
case 'address':
|
case 'address':
|
||||||
return Staff(
|
return Staff(
|
||||||
@@ -100,7 +99,6 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
|
|||||||
status: staff.status,
|
status: staff.status,
|
||||||
address: value,
|
address: value,
|
||||||
avatar: staff.avatar,
|
avatar: staff.avatar,
|
||||||
livePhoto: staff.livePhoto,
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return staff;
|
return staff;
|
||||||
|
|||||||
Reference in New Issue
Block a user