feat: implement emergency contact management with Firebase integration and relationship type handling
This commit is contained in:
@@ -1,25 +1,83 @@
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/repositories/emergency_contact_repository_interface.dart';
|
||||
|
||||
/// Implementation of [EmergencyContactRepositoryInterface].
|
||||
///
|
||||
/// This repository delegates data operations to the [ProfileRepositoryMock]
|
||||
/// (or real implementation) from the `data_connect` package.
|
||||
/// This repository delegates data operations to Firebase Data Connect.
|
||||
class EmergencyContactRepositoryImpl
|
||||
implements EmergencyContactRepositoryInterface {
|
||||
final ProfileRepositoryMock _profileRepository;
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
final FirebaseAuth _firebaseAuth;
|
||||
|
||||
/// Creates an [EmergencyContactRepositoryImpl].
|
||||
EmergencyContactRepositoryImpl(this._profileRepository);
|
||||
EmergencyContactRepositoryImpl({
|
||||
required dc.ExampleConnector dataConnect,
|
||||
required FirebaseAuth firebaseAuth,
|
||||
}) : _dataConnect = dataConnect,
|
||||
_firebaseAuth = firebaseAuth;
|
||||
|
||||
@override
|
||||
Future<List<EmergencyContact>> getContacts(String staffId) {
|
||||
return _profileRepository.getEmergencyContacts(staffId);
|
||||
Future<String> _getStaffId() async {
|
||||
final user = _firebaseAuth.currentUser;
|
||||
if (user == null) throw Exception('User not authenticated');
|
||||
|
||||
final result =
|
||||
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
|
||||
if (result.data.staffs.isEmpty) {
|
||||
throw Exception('Staff profile not found');
|
||||
}
|
||||
return result.data.staffs.first.id;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveContacts(String staffId, List<EmergencyContact> contacts) {
|
||||
return _profileRepository.saveEmergencyContacts(staffId, contacts);
|
||||
Future<List<EmergencyContact>> getContacts() async {
|
||||
final staffId = await _getStaffId();
|
||||
final result =
|
||||
await _dataConnect.getEmergencyContactsByStaffId(staffId: staffId).execute();
|
||||
|
||||
return result.data.emergencyContacts.map((dto) {
|
||||
return EmergencyContactAdapter.fromPrimitives(
|
||||
id: dto.id,
|
||||
name: dto.name,
|
||||
phone: dto.phone,
|
||||
relationship: dto.relationship.stringValue,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveContacts(List<EmergencyContact> contacts) async {
|
||||
final staffId = await _getStaffId();
|
||||
|
||||
// 1. Get existing to delete
|
||||
final existingResult =
|
||||
await _dataConnect.getEmergencyContactsByStaffId(staffId: staffId).execute();
|
||||
final existingIds =
|
||||
existingResult.data.emergencyContacts.map((e) => e.id).toList();
|
||||
|
||||
// 2. Delete all existing
|
||||
await Future.wait(existingIds.map(
|
||||
(id) => _dataConnect.deleteEmergencyContact(id: id).execute()));
|
||||
|
||||
// 3. Create new
|
||||
await Future.wait(contacts.map((contact) {
|
||||
dc.RelationshipType rel = dc.RelationshipType.OTHER;
|
||||
switch(contact.relationship) {
|
||||
case RelationshipType.family: rel = dc.RelationshipType.FAMILY; break;
|
||||
case RelationshipType.spouse: rel = dc.RelationshipType.SPOUSE; break;
|
||||
case RelationshipType.friend: rel = dc.RelationshipType.FRIEND; break;
|
||||
case RelationshipType.other: rel = dc.RelationshipType.OTHER; break;
|
||||
}
|
||||
|
||||
return _dataConnect
|
||||
.createEmergencyContact(
|
||||
name: contact.name,
|
||||
phone: contact.phone,
|
||||
relationship: rel,
|
||||
staffId: staffId,
|
||||
)
|
||||
.execute();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,9 @@ import 'package:krow_core/core.dart';
|
||||
|
||||
/// Arguments for getting emergency contacts use case.
|
||||
class GetEmergencyContactsArguments extends UseCaseArgument {
|
||||
/// The ID of the staff member.
|
||||
final String staffId;
|
||||
|
||||
/// Creates a [GetEmergencyContactsArguments].
|
||||
const GetEmergencyContactsArguments({required this.staffId});
|
||||
const GetEmergencyContactsArguments();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [staffId];
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
@@ -3,18 +3,14 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Arguments for saving emergency contacts use case.
|
||||
class SaveEmergencyContactsArguments extends UseCaseArgument {
|
||||
/// The ID of the staff member.
|
||||
final String staffId;
|
||||
|
||||
/// The list of contacts to save.
|
||||
final List<EmergencyContact> contacts;
|
||||
|
||||
/// Creates a [SaveEmergencyContactsArguments].
|
||||
const SaveEmergencyContactsArguments({
|
||||
required this.staffId,
|
||||
required this.contacts,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [staffId, contacts];
|
||||
List<Object?> get props => [contacts];
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
extension EmergencyContactExtensions on EmergencyContact {
|
||||
/// returns a copy of this [EmergencyContact] with the given fields replaced.
|
||||
EmergencyContact copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? phone,
|
||||
String? relationship,
|
||||
RelationshipType? relationship,
|
||||
}) {
|
||||
return EmergencyContact(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
phone: phone ?? this.phone,
|
||||
relationship: relationship ?? this.relationship,
|
||||
@@ -18,9 +20,11 @@ extension EmergencyContactExtensions on EmergencyContact {
|
||||
/// Returns an empty [EmergencyContact].
|
||||
static EmergencyContact empty() {
|
||||
return const EmergencyContact(
|
||||
id: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
relationship: 'family',
|
||||
relationship: RelationshipType.family,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
/// It must be implemented by the data layer.
|
||||
abstract class EmergencyContactRepositoryInterface {
|
||||
/// Retrieves the list of emergency contacts.
|
||||
Future<List<EmergencyContact>> getContacts(String staffId);
|
||||
Future<List<EmergencyContact>> getContacts();
|
||||
|
||||
/// Saves the list of emergency contacts.
|
||||
Future<void> saveContacts(String staffId, List<EmergencyContact> contacts);
|
||||
Future<void> saveContacts(List<EmergencyContact> contacts);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,6 @@ class GetEmergencyContactsUseCase
|
||||
|
||||
@override
|
||||
Future<List<EmergencyContact>> call(GetEmergencyContactsArguments params) {
|
||||
return _repository.getContacts(params.staffId);
|
||||
return _repository.getContacts();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@ class SaveEmergencyContactsUseCase
|
||||
|
||||
@override
|
||||
Future<void> call(SaveEmergencyContactsArguments params) {
|
||||
return _repository.saveContacts(params.staffId, params.contacts);
|
||||
return _repository.saveContacts(params.contacts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,10 @@ class EmergencyContactBloc
|
||||
extends Bloc<EmergencyContactEvent, EmergencyContactState> {
|
||||
final GetEmergencyContactsUseCase getEmergencyContacts;
|
||||
final SaveEmergencyContactsUseCase saveEmergencyContacts;
|
||||
final String staffId;
|
||||
|
||||
EmergencyContactBloc({
|
||||
required this.getEmergencyContacts,
|
||||
required this.saveEmergencyContacts,
|
||||
required this.staffId,
|
||||
}) : super(const EmergencyContactState()) {
|
||||
on<EmergencyContactsLoaded>(_onLoaded);
|
||||
on<EmergencyContactAdded>(_onAdded);
|
||||
@@ -102,13 +100,13 @@ class EmergencyContactBloc
|
||||
emit(state.copyWith(status: EmergencyContactStatus.loading));
|
||||
try {
|
||||
final contacts = await getEmergencyContacts(
|
||||
GetEmergencyContactsArguments(staffId: staffId),
|
||||
const GetEmergencyContactsArguments(),
|
||||
);
|
||||
emit(state.copyWith(
|
||||
status: EmergencyContactStatus.success,
|
||||
contacts: contacts.isNotEmpty
|
||||
? contacts
|
||||
: [const EmergencyContact(name: '', phone: '', relationship: 'family')],
|
||||
: [EmergencyContact.empty()],
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
@@ -123,7 +121,7 @@ class EmergencyContactBloc
|
||||
Emitter<EmergencyContactState> emit,
|
||||
) {
|
||||
final updatedContacts = List<EmergencyContact>.from(state.contacts)
|
||||
..add(const EmergencyContact(name: '', phone: '', relationship: 'family'));
|
||||
..add(EmergencyContact.empty());
|
||||
emit(state.copyWith(contacts: updatedContacts));
|
||||
}
|
||||
|
||||
@@ -153,7 +151,6 @@ class EmergencyContactBloc
|
||||
try {
|
||||
await saveEmergencyContacts(
|
||||
SaveEmergencyContactsArguments(
|
||||
staffId: staffId,
|
||||
contacts: state.contacts,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/extensions/emergency_contact_extensions.dart';
|
||||
import '../blocs/emergency_contact_bloc.dart';
|
||||
|
||||
class EmergencyContactFormItem extends StatelessWidget {
|
||||
@@ -62,7 +61,7 @@ class EmergencyContactFormItem extends StatelessWidget {
|
||||
_buildDropdown(
|
||||
context,
|
||||
value: contact.relationship,
|
||||
items: const ['family', 'friend', 'partner', 'other'],
|
||||
items: RelationshipType.values,
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
context.read<EmergencyContactBloc>().add(
|
||||
@@ -79,6 +78,52 @@ class EmergencyContactFormItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdown(
|
||||
BuildContext context, {
|
||||
required RelationshipType value,
|
||||
required List<RelationshipType> items,
|
||||
required ValueChanged<RelationshipType?> onChanged,
|
||||
}) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<RelationshipType>(
|
||||
value: value,
|
||||
isExpanded: true,
|
||||
dropdownColor: UiColors.bgPopup,
|
||||
icon: Icon(UiIcons.chevronDown, color: UiColors.iconSecondary),
|
||||
items: items.map((type) {
|
||||
return DropdownMenuItem<RelationshipType>(
|
||||
value: type,
|
||||
child: Text(
|
||||
_formatRelationship(type),
|
||||
style: UiTypography.body1r.copyWith(color: UiColors.textPrimary),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatRelationship(RelationshipType type) {
|
||||
switch(type) {
|
||||
case RelationshipType.family: return 'Family';
|
||||
case RelationshipType.spouse: return 'Spouse';
|
||||
case RelationshipType.friend: return 'Friend';
|
||||
case RelationshipType.other: return 'Other';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -150,39 +195,5 @@ class EmergencyContactFormItem extends StatelessWidget {
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdown(
|
||||
BuildContext context, {
|
||||
required String value,
|
||||
required List<String> items,
|
||||
required Function(String?) onChanged,
|
||||
}) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
border: Border.all(color: UiColors.border),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
value: items.contains(value) ? value : items.first,
|
||||
isExpanded: true,
|
||||
icon: Icon(UiIcons.chevronDown, color: UiColors.textSecondary),
|
||||
items: items.map((String item) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: item,
|
||||
child: Text(
|
||||
item.toUpperCase(),
|
||||
style: UiTypography.body1r.copyWith(
|
||||
color: UiColors.textPrimary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: onChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'data/repositories/emergency_contact_repository_impl.dart';
|
||||
@@ -11,10 +12,11 @@ class StaffEmergencyContactModule extends Module {
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repository
|
||||
// Uses ProfileRepositoryMock from data_connect
|
||||
i.addLazySingleton<ProfileRepositoryMock>(ProfileRepositoryMock.new);
|
||||
i.addLazySingleton<EmergencyContactRepositoryInterface>(
|
||||
() => EmergencyContactRepositoryImpl(i.get<ProfileRepositoryMock>()),
|
||||
() => EmergencyContactRepositoryImpl(
|
||||
dataConnect: ExampleConnector.instance,
|
||||
firebaseAuth: FirebaseAuth.instance,
|
||||
),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
@@ -30,7 +32,6 @@ class StaffEmergencyContactModule extends Module {
|
||||
() => EmergencyContactBloc(
|
||||
getEmergencyContacts: i.get<GetEmergencyContactsUseCase>(),
|
||||
saveEmergencyContacts: i.get<SaveEmergencyContactsUseCase>(),
|
||||
staffId: 'mock-staff-id', // TODO: Get direct from auth state
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user