Merge branch '216-p0-staff-01-profile-setup-wizard' into coverage_screen_app
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
name: krowwithus_client
|
||||
description: "Krow Client Application"
|
||||
publish_to: 'none'
|
||||
version: 0.0.1+M301
|
||||
publish_to: "none"
|
||||
version: 0.0.1-M+301
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
|
||||
@@ -79,7 +79,12 @@
|
||||
"cleaning": "Cleaning",
|
||||
"security": "Security",
|
||||
"driving": "Driving",
|
||||
"cooking": "Cooking"
|
||||
"cooking": "Cooking",
|
||||
"cashier": "Cashier",
|
||||
"server": "Server",
|
||||
"barista": "Barista",
|
||||
"host_hostess": "Host/Hostess",
|
||||
"busser": "Busser"
|
||||
},
|
||||
"industries": {
|
||||
"hospitality": "Hospitality",
|
||||
@@ -577,7 +582,8 @@
|
||||
"server": "Server",
|
||||
"barista": "Barista",
|
||||
"host_hostess": "Host/Hostess",
|
||||
"busser": "Busser"
|
||||
"busser": "Busser",
|
||||
"driving": "Driving"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,12 @@
|
||||
"cleaning": "Limpieza",
|
||||
"security": "Seguridad",
|
||||
"driving": "Conducción",
|
||||
"cooking": "Cocina"
|
||||
"cooking": "Cocina",
|
||||
"cashier": "Cajero",
|
||||
"server": "Mesero",
|
||||
"barista": "Barista",
|
||||
"host_hostess": "Anfitrión",
|
||||
"busser": "Ayudante de mesero"
|
||||
},
|
||||
"industries": {
|
||||
"hospitality": "Hostelería",
|
||||
@@ -576,7 +581,8 @@
|
||||
"server": "Server",
|
||||
"barista": "Barista",
|
||||
"host_hostess": "Host/Hostess",
|
||||
"busser": "Busser"
|
||||
"busser": "Busser",
|
||||
"driving": "Driving"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
/// To regenerate, run: `dart run slang`
|
||||
///
|
||||
/// Locales: 2
|
||||
/// Strings: 1026 (513 per locale)
|
||||
/// Strings: 1038 (519 per locale)
|
||||
///
|
||||
/// Built on 2026-01-27 at 00:15 UTC
|
||||
/// Built on 2026-01-27 at 19:37 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -2420,6 +2420,21 @@ class TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEn {
|
||||
|
||||
/// en: 'Cooking'
|
||||
String get cooking => 'Cooking';
|
||||
|
||||
/// en: 'Cashier'
|
||||
String get cashier => 'Cashier';
|
||||
|
||||
/// en: 'Server'
|
||||
String get server => 'Server';
|
||||
|
||||
/// en: 'Barista'
|
||||
String get barista => 'Barista';
|
||||
|
||||
/// en: 'Host/Hostess'
|
||||
String get host_hostess => 'Host/Hostess';
|
||||
|
||||
/// en: 'Busser'
|
||||
String get busser => 'Busser';
|
||||
}
|
||||
|
||||
// Path: staff_authentication.profile_setup_page.experience.industries
|
||||
@@ -2589,6 +2604,9 @@ class TranslationsStaffOnboardingExperienceSkillsEn {
|
||||
|
||||
/// en: 'Busser'
|
||||
String get busser => 'Busser';
|
||||
|
||||
/// en: 'Driving'
|
||||
String get driving => 'Driving';
|
||||
}
|
||||
|
||||
// Path: staff.home.improve.items.training
|
||||
@@ -2724,6 +2742,11 @@ extension on Translations {
|
||||
'staff_authentication.profile_setup_page.experience.skills.security' => 'Security',
|
||||
'staff_authentication.profile_setup_page.experience.skills.driving' => 'Driving',
|
||||
'staff_authentication.profile_setup_page.experience.skills.cooking' => 'Cooking',
|
||||
'staff_authentication.profile_setup_page.experience.skills.cashier' => 'Cashier',
|
||||
'staff_authentication.profile_setup_page.experience.skills.server' => 'Server',
|
||||
'staff_authentication.profile_setup_page.experience.skills.barista' => 'Barista',
|
||||
'staff_authentication.profile_setup_page.experience.skills.host_hostess' => 'Host/Hostess',
|
||||
'staff_authentication.profile_setup_page.experience.skills.busser' => 'Busser',
|
||||
'staff_authentication.profile_setup_page.experience.industries.hospitality' => 'Hospitality',
|
||||
'staff_authentication.profile_setup_page.experience.industries.food_service' => 'Food Service',
|
||||
'staff_authentication.profile_setup_page.experience.industries.warehouse' => 'Warehouse',
|
||||
@@ -3090,6 +3113,7 @@ extension on Translations {
|
||||
'staff.onboarding.experience.skills.barista' => 'Barista',
|
||||
'staff.onboarding.experience.skills.host_hostess' => 'Host/Hostess',
|
||||
'staff.onboarding.experience.skills.busser' => 'Busser',
|
||||
'staff.onboarding.experience.skills.driving' => 'Driving',
|
||||
'staff_documents.title' => 'Documents',
|
||||
'staff_documents.verification_card.title' => 'Document Verification',
|
||||
'staff_documents.verification_card.progress' => ({required Object completed, required Object total}) => '${completed}/${total} Complete',
|
||||
@@ -3171,14 +3195,14 @@ extension on Translations {
|
||||
'staff_time_card.hours_worked' => 'Hours Worked',
|
||||
'staff_time_card.total_earnings' => 'Total Earnings',
|
||||
'staff_time_card.shift_history' => 'Shift History',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'staff_time_card.no_shifts' => 'No shifts for this month',
|
||||
'staff_time_card.hours' => 'hours',
|
||||
'staff_time_card.per_hr' => '/hr',
|
||||
'staff_time_card.status.approved' => 'Approved',
|
||||
'staff_time_card.status.disputed' => 'Disputed',
|
||||
'staff_time_card.status.paid' => 'Paid',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'staff_time_card.status.pending' => 'Pending',
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -1463,6 +1463,11 @@ class _TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEs impleme
|
||||
@override String get security => 'Seguridad';
|
||||
@override String get driving => 'Conducción';
|
||||
@override String get cooking => 'Cocina';
|
||||
@override String get cashier => 'Cajero';
|
||||
@override String get server => 'Mesero';
|
||||
@override String get barista => 'Barista';
|
||||
@override String get host_hostess => 'Anfitrión';
|
||||
@override String get busser => 'Ayudante de mesero';
|
||||
}
|
||||
|
||||
// Path: staff_authentication.profile_setup_page.experience.industries
|
||||
@@ -1564,6 +1569,7 @@ class _TranslationsStaffOnboardingExperienceSkillsEs implements TranslationsStaf
|
||||
@override String get barista => 'Barista';
|
||||
@override String get host_hostess => 'Host/Hostess';
|
||||
@override String get busser => 'Busser';
|
||||
@override String get driving => 'Driving';
|
||||
}
|
||||
|
||||
// Path: staff.home.improve.items.training
|
||||
@@ -1679,6 +1685,11 @@ extension on TranslationsEs {
|
||||
'staff_authentication.profile_setup_page.experience.skills.security' => 'Seguridad',
|
||||
'staff_authentication.profile_setup_page.experience.skills.driving' => 'Conducción',
|
||||
'staff_authentication.profile_setup_page.experience.skills.cooking' => 'Cocina',
|
||||
'staff_authentication.profile_setup_page.experience.skills.cashier' => 'Cajero',
|
||||
'staff_authentication.profile_setup_page.experience.skills.server' => 'Mesero',
|
||||
'staff_authentication.profile_setup_page.experience.skills.barista' => 'Barista',
|
||||
'staff_authentication.profile_setup_page.experience.skills.host_hostess' => 'Anfitrión',
|
||||
'staff_authentication.profile_setup_page.experience.skills.busser' => 'Ayudante de mesero',
|
||||
'staff_authentication.profile_setup_page.experience.industries.hospitality' => 'Hostelería',
|
||||
'staff_authentication.profile_setup_page.experience.industries.food_service' => 'Servicio de comida',
|
||||
'staff_authentication.profile_setup_page.experience.industries.warehouse' => 'Almacén',
|
||||
@@ -2045,6 +2056,7 @@ extension on TranslationsEs {
|
||||
'staff.onboarding.experience.skills.barista' => 'Barista',
|
||||
'staff.onboarding.experience.skills.host_hostess' => 'Host/Hostess',
|
||||
'staff.onboarding.experience.skills.busser' => 'Busser',
|
||||
'staff.onboarding.experience.skills.driving' => 'Driving',
|
||||
'staff_documents.title' => 'Documents',
|
||||
'staff_documents.verification_card.title' => 'Document Verification',
|
||||
'staff_documents.verification_card.progress' => ({required Object completed, required Object total}) => '${completed}/${total} Complete',
|
||||
@@ -2126,14 +2138,14 @@ extension on TranslationsEs {
|
||||
'staff_time_card.hours_worked' => 'Horas trabajadas',
|
||||
'staff_time_card.total_earnings' => 'Ganancias totales',
|
||||
'staff_time_card.shift_history' => 'Historial de turnos',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'staff_time_card.no_shifts' => 'No hay turnos para este mes',
|
||||
'staff_time_card.hours' => 'horas',
|
||||
'staff_time_card.per_hr' => '/hr',
|
||||
'staff_time_card.status.approved' => 'Aprobado',
|
||||
'staff_time_card.status.disputed' => 'Disputado',
|
||||
'staff_time_card.status.paid' => 'Pagado',
|
||||
_ => null,
|
||||
} ?? switch (path) {
|
||||
'staff_time_card.status.pending' => 'Pendiente',
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# Basic Usage
|
||||
|
||||
```dart
|
||||
ExampleConnector.instance.listActivityLogs(listActivityLogsVariables).execute();
|
||||
ExampleConnector.instance.getActivityLogById(getActivityLogByIdVariables).execute();
|
||||
ExampleConnector.instance.listActivityLogsByUserId(listActivityLogsByUserIdVariables).execute();
|
||||
ExampleConnector.instance.listUnreadActivityLogsByUserId(listUnreadActivityLogsByUserIdVariables).execute();
|
||||
ExampleConnector.instance.filterActivityLogs(filterActivityLogsVariables).execute();
|
||||
ExampleConnector.instance.listConversations(listConversationsVariables).execute();
|
||||
ExampleConnector.instance.getConversationById(getConversationByIdVariables).execute();
|
||||
ExampleConnector.instance.listConversationsByType(listConversationsByTypeVariables).execute();
|
||||
ExampleConnector.instance.listConversationsByStatus(listConversationsByStatusVariables).execute();
|
||||
ExampleConnector.instance.filterConversations(filterConversationsVariables).execute();
|
||||
ExampleConnector.instance.createTeamHudDepartment(createTeamHudDepartmentVariables).execute();
|
||||
ExampleConnector.instance.updateTeamHudDepartment(updateTeamHudDepartmentVariables).execute();
|
||||
ExampleConnector.instance.deleteTeamHudDepartment(deleteTeamHudDepartmentVariables).execute();
|
||||
ExampleConnector.instance.CreateUser(createUserVariables).execute();
|
||||
ExampleConnector.instance.UpdateUser(updateUserVariables).execute();
|
||||
ExampleConnector.instance.DeleteUser(deleteUserVariables).execute();
|
||||
ExampleConnector.instance.createVendorBenefitPlan(createVendorBenefitPlanVariables).execute();
|
||||
ExampleConnector.instance.updateVendorBenefitPlan(updateVendorBenefitPlanVariables).execute();
|
||||
ExampleConnector.instance.deleteVendorBenefitPlan(deleteVendorBenefitPlanVariables).execute();
|
||||
ExampleConnector.instance.getShiftRoleById(getShiftRoleByIdVariables).execute();
|
||||
|
||||
```
|
||||
|
||||
@@ -23,8 +23,8 @@ Optional fields can be discovered based on classes that have `Optional` object t
|
||||
This is an example of a mutation with an optional field:
|
||||
|
||||
```dart
|
||||
await ExampleConnector.instance.createStaffRole({ ... })
|
||||
.roleType(...)
|
||||
await ExampleConnector.instance.updateStaffAvailabilityStats({ ... })
|
||||
.needWorkIndex(...)
|
||||
.execute();
|
||||
```
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,8 +15,8 @@ class CreateStaffVariablesBuilder {
|
||||
Optional<int> _cancellationCount = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<int> _reliabilityScore = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<String> _bio = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<AnyValue> _skills = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
Optional<AnyValue> _industries = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
Optional<List<String>> _skills = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
Optional<List<String>> _industries = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
Optional<List<String>> _preferredLocations = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
Optional<int> _maxDistanceMiles = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<AnyValue> _languages = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
@@ -84,11 +84,11 @@ class CreateStaffVariablesBuilder {
|
||||
_bio.value = t;
|
||||
return this;
|
||||
}
|
||||
CreateStaffVariablesBuilder skills(AnyValue? t) {
|
||||
CreateStaffVariablesBuilder skills(List<String>? t) {
|
||||
_skills.value = t;
|
||||
return this;
|
||||
}
|
||||
CreateStaffVariablesBuilder industries(AnyValue? t) {
|
||||
CreateStaffVariablesBuilder industries(List<String>? t) {
|
||||
_industries.value = t;
|
||||
return this;
|
||||
}
|
||||
@@ -262,8 +262,8 @@ class CreateStaffVariables {
|
||||
late final Optional<int>cancellationCount;
|
||||
late final Optional<int>reliabilityScore;
|
||||
late final Optional<String>bio;
|
||||
late final Optional<AnyValue>skills;
|
||||
late final Optional<AnyValue>industries;
|
||||
late final Optional<List<String>>skills;
|
||||
late final Optional<List<String>>industries;
|
||||
late final Optional<List<String>>preferredLocations;
|
||||
late final Optional<int>maxDistanceMiles;
|
||||
late final Optional<AnyValue>languages;
|
||||
@@ -339,12 +339,16 @@ class CreateStaffVariables {
|
||||
bio.value = json['bio'] == null ? null : nativeFromJson<String>(json['bio']);
|
||||
|
||||
|
||||
skills = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
skills.value = json['skills'] == null ? null : AnyValue.fromJson(json['skills']);
|
||||
skills = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
skills.value = json['skills'] == null ? null : (json['skills'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList();
|
||||
|
||||
|
||||
industries = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
industries.value = json['industries'] == null ? null : AnyValue.fromJson(json['industries']);
|
||||
industries = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
industries.value = json['industries'] == null ? null : (json['industries'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList();
|
||||
|
||||
|
||||
preferredLocations = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
|
||||
@@ -51,7 +51,7 @@ class FilterStaffStaffs {
|
||||
final int? totalShifts;
|
||||
final String? ownerId;
|
||||
final bool? isRecommended;
|
||||
final AnyValue? skills;
|
||||
final List<String>? skills;
|
||||
final EnumValue<BackgroundCheckStatus>? backgroundCheckStatus;
|
||||
final EnumValue<EmploymentType>? employmentType;
|
||||
final String? initial;
|
||||
@@ -72,7 +72,9 @@ class FilterStaffStaffs {
|
||||
totalShifts = json['totalShifts'] == null ? null : nativeFromJson<int>(json['totalShifts']),
|
||||
ownerId = json['ownerId'] == null ? null : nativeFromJson<String>(json['ownerId']),
|
||||
isRecommended = json['isRecommended'] == null ? null : nativeFromJson<bool>(json['isRecommended']),
|
||||
skills = json['skills'] == null ? null : AnyValue.fromJson(json['skills']),
|
||||
skills = json['skills'] == null ? null : (json['skills'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
backgroundCheckStatus = json['backgroundCheckStatus'] == null ? null : backgroundCheckStatusDeserializer(json['backgroundCheckStatus']),
|
||||
employmentType = json['employmentType'] == null ? null : employmentTypeDeserializer(json['employmentType']),
|
||||
initial = json['initial'] == null ? null : nativeFromJson<String>(json['initial']),
|
||||
@@ -147,7 +149,7 @@ class FilterStaffStaffs {
|
||||
json['isRecommended'] = nativeToJson<bool?>(isRecommended);
|
||||
}
|
||||
if (skills != null) {
|
||||
json['skills'] = skills!.toJson();
|
||||
json['skills'] = skills?.map((e) => nativeToJson<String>(e)).toList();
|
||||
}
|
||||
if (backgroundCheckStatus != null) {
|
||||
json['backgroundCheckStatus'] =
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,8 +37,8 @@ class GetStaffByIdStaff {
|
||||
final AnyValue? badges;
|
||||
final bool? isRecommended;
|
||||
final String? bio;
|
||||
final AnyValue? skills;
|
||||
final AnyValue? industries;
|
||||
final List<String>? skills;
|
||||
final List<String>? industries;
|
||||
final List<String>? preferredLocations;
|
||||
final int? maxDistanceMiles;
|
||||
final AnyValue? languages;
|
||||
@@ -77,8 +77,12 @@ class GetStaffByIdStaff {
|
||||
badges = json['badges'] == null ? null : AnyValue.fromJson(json['badges']),
|
||||
isRecommended = json['isRecommended'] == null ? null : nativeFromJson<bool>(json['isRecommended']),
|
||||
bio = json['bio'] == null ? null : nativeFromJson<String>(json['bio']),
|
||||
skills = json['skills'] == null ? null : AnyValue.fromJson(json['skills']),
|
||||
industries = json['industries'] == null ? null : AnyValue.fromJson(json['industries']),
|
||||
skills = json['skills'] == null ? null : (json['skills'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
industries = json['industries'] == null ? null : (json['industries'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
preferredLocations = json['preferredLocations'] == null ? null : (json['preferredLocations'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
@@ -204,10 +208,10 @@ class GetStaffByIdStaff {
|
||||
json['bio'] = nativeToJson<String?>(bio);
|
||||
}
|
||||
if (skills != null) {
|
||||
json['skills'] = skills!.toJson();
|
||||
json['skills'] = skills?.map((e) => nativeToJson<String>(e)).toList();
|
||||
}
|
||||
if (industries != null) {
|
||||
json['industries'] = industries!.toJson();
|
||||
json['industries'] = industries?.map((e) => nativeToJson<String>(e)).toList();
|
||||
}
|
||||
if (preferredLocations != null) {
|
||||
json['preferredLocations'] = preferredLocations?.map((e) => nativeToJson<String>(e)).toList();
|
||||
|
||||
@@ -36,8 +36,8 @@ class GetStaffByUserIdStaffs {
|
||||
final AnyValue? badges;
|
||||
final bool? isRecommended;
|
||||
final String? bio;
|
||||
final AnyValue? skills;
|
||||
final AnyValue? industries;
|
||||
final List<String>? skills;
|
||||
final List<String>? industries;
|
||||
final List<String>? preferredLocations;
|
||||
final int? maxDistanceMiles;
|
||||
final AnyValue? languages;
|
||||
@@ -75,8 +75,12 @@ class GetStaffByUserIdStaffs {
|
||||
badges = json['badges'] == null ? null : AnyValue.fromJson(json['badges']),
|
||||
isRecommended = json['isRecommended'] == null ? null : nativeFromJson<bool>(json['isRecommended']),
|
||||
bio = json['bio'] == null ? null : nativeFromJson<String>(json['bio']),
|
||||
skills = json['skills'] == null ? null : AnyValue.fromJson(json['skills']),
|
||||
industries = json['industries'] == null ? null : AnyValue.fromJson(json['industries']),
|
||||
skills = json['skills'] == null ? null : (json['skills'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
industries = json['industries'] == null ? null : (json['industries'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
preferredLocations = json['preferredLocations'] == null ? null : (json['preferredLocations'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
@@ -198,10 +202,10 @@ class GetStaffByUserIdStaffs {
|
||||
json['bio'] = nativeToJson<String?>(bio);
|
||||
}
|
||||
if (skills != null) {
|
||||
json['skills'] = skills!.toJson();
|
||||
json['skills'] = skills?.map((e) => nativeToJson<String>(e)).toList();
|
||||
}
|
||||
if (industries != null) {
|
||||
json['industries'] = industries!.toJson();
|
||||
json['industries'] = industries?.map((e) => nativeToJson<String>(e)).toList();
|
||||
}
|
||||
if (preferredLocations != null) {
|
||||
json['preferredLocations'] = preferredLocations?.map((e) => nativeToJson<String>(e)).toList();
|
||||
|
||||
@@ -36,8 +36,8 @@ class ListStaffStaffs {
|
||||
final AnyValue? badges;
|
||||
final bool? isRecommended;
|
||||
final String? bio;
|
||||
final AnyValue? skills;
|
||||
final AnyValue? industries;
|
||||
final List<String>? skills;
|
||||
final List<String>? industries;
|
||||
final List<String>? preferredLocations;
|
||||
final int? maxDistanceMiles;
|
||||
final AnyValue? languages;
|
||||
@@ -74,8 +74,12 @@ class ListStaffStaffs {
|
||||
badges = json['badges'] == null ? null : AnyValue.fromJson(json['badges']),
|
||||
isRecommended = json['isRecommended'] == null ? null : nativeFromJson<bool>(json['isRecommended']),
|
||||
bio = json['bio'] == null ? null : nativeFromJson<String>(json['bio']),
|
||||
skills = json['skills'] == null ? null : AnyValue.fromJson(json['skills']),
|
||||
industries = json['industries'] == null ? null : AnyValue.fromJson(json['industries']),
|
||||
skills = json['skills'] == null ? null : (json['skills'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
industries = json['industries'] == null ? null : (json['industries'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
preferredLocations = json['preferredLocations'] == null ? null : (json['preferredLocations'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList(),
|
||||
@@ -197,10 +201,10 @@ class ListStaffStaffs {
|
||||
json['bio'] = nativeToJson<String?>(bio);
|
||||
}
|
||||
if (skills != null) {
|
||||
json['skills'] = skills!.toJson();
|
||||
json['skills'] = skills?.map((e) => nativeToJson<String>(e)).toList();
|
||||
}
|
||||
if (industries != null) {
|
||||
json['industries'] = industries!.toJson();
|
||||
json['industries'] = industries?.map((e) => nativeToJson<String>(e)).toList();
|
||||
}
|
||||
if (preferredLocations != null) {
|
||||
json['preferredLocations'] = preferredLocations?.map((e) => nativeToJson<String>(e)).toList();
|
||||
|
||||
@@ -16,8 +16,8 @@ class UpdateStaffVariablesBuilder {
|
||||
Optional<int> _cancellationCount = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<int> _reliabilityScore = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<String> _bio = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<AnyValue> _skills = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
Optional<AnyValue> _industries = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
Optional<List<String>> _skills = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
Optional<List<String>> _industries = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
Optional<List<String>> _preferredLocations = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
Optional<int> _maxDistanceMiles = Optional.optional(nativeFromJson, nativeToJson);
|
||||
Optional<AnyValue> _languages = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
@@ -93,11 +93,11 @@ class UpdateStaffVariablesBuilder {
|
||||
_bio.value = t;
|
||||
return this;
|
||||
}
|
||||
UpdateStaffVariablesBuilder skills(AnyValue? t) {
|
||||
UpdateStaffVariablesBuilder skills(List<String>? t) {
|
||||
_skills.value = t;
|
||||
return this;
|
||||
}
|
||||
UpdateStaffVariablesBuilder industries(AnyValue? t) {
|
||||
UpdateStaffVariablesBuilder industries(List<String>? t) {
|
||||
_industries.value = t;
|
||||
return this;
|
||||
}
|
||||
@@ -274,8 +274,8 @@ class UpdateStaffVariables {
|
||||
late final Optional<int>cancellationCount;
|
||||
late final Optional<int>reliabilityScore;
|
||||
late final Optional<String>bio;
|
||||
late final Optional<AnyValue>skills;
|
||||
late final Optional<AnyValue>industries;
|
||||
late final Optional<List<String>>skills;
|
||||
late final Optional<List<String>>industries;
|
||||
late final Optional<List<String>>preferredLocations;
|
||||
late final Optional<int>maxDistanceMiles;
|
||||
late final Optional<AnyValue>languages;
|
||||
@@ -357,12 +357,16 @@ class UpdateStaffVariables {
|
||||
bio.value = json['bio'] == null ? null : nativeFromJson<String>(json['bio']);
|
||||
|
||||
|
||||
skills = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
skills.value = json['skills'] == null ? null : AnyValue.fromJson(json['skills']);
|
||||
skills = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
skills.value = json['skills'] == null ? null : (json['skills'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList();
|
||||
|
||||
|
||||
industries = Optional.optional(AnyValue.fromJson, defaultSerializer);
|
||||
industries.value = json['industries'] == null ? null : AnyValue.fromJson(json['industries']);
|
||||
industries = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
industries.value = json['industries'] == null ? null : (json['industries'] as List<dynamic>)
|
||||
.map((e) => nativeFromJson<String>(e))
|
||||
.toList();
|
||||
|
||||
|
||||
preferredLocations = Optional.optional(listDeserializer(nativeFromJson), listSerializer(nativeToJson));
|
||||
|
||||
@@ -51,7 +51,8 @@ class ProfileRepositoryMock {
|
||||
const EmergencyContact(
|
||||
name: 'Jane Doe',
|
||||
phone: '555-987-6543',
|
||||
relationship: 'Family',
|
||||
relationship: RelationshipType.spouse,
|
||||
id: 'contact_1',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ export 'src/entities/financial/staff_payment.dart';
|
||||
// Profile
|
||||
export 'src/entities/profile/staff_document.dart';
|
||||
export 'src/entities/profile/attire_item.dart';
|
||||
export 'src/entities/profile/relationship_type.dart';
|
||||
export 'src/entities/profile/industry.dart';
|
||||
|
||||
// Ratings & Penalties
|
||||
export 'src/entities/ratings/staff_rating.dart';
|
||||
@@ -77,3 +79,9 @@ export 'src/entities/home/reorder_item.dart';
|
||||
// Availability
|
||||
export 'src/entities/availability/availability_slot.dart';
|
||||
export 'src/entities/availability/day_availability.dart';
|
||||
|
||||
// Adapters
|
||||
export 'src/adapters/profile/emergency_contact_adapter.dart';
|
||||
export 'src/adapters/profile/experience_adapter.dart';
|
||||
export 'src/entities/profile/experience_skill.dart';
|
||||
export 'src/adapters/profile/bank_account_adapter.dart';
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import '../../entities/profile/bank_account.dart';
|
||||
|
||||
/// Adapter for [BankAccount] to map data layer values to domain entity.
|
||||
class BankAccountAdapter {
|
||||
/// Maps primitive values to [BankAccount].
|
||||
static BankAccount fromPrimitives({
|
||||
required String id,
|
||||
required String userId,
|
||||
required String bankName,
|
||||
required String? type,
|
||||
String? accountNumber,
|
||||
String? last4,
|
||||
String? sortCode,
|
||||
bool? isPrimary,
|
||||
}) {
|
||||
return BankAccount(
|
||||
id: id,
|
||||
userId: userId,
|
||||
bankName: bankName,
|
||||
accountNumber: accountNumber ?? '',
|
||||
accountName: '', // Not provided by backend
|
||||
last4: last4,
|
||||
sortCode: sortCode,
|
||||
type: _stringToType(type),
|
||||
isPrimary: isPrimary ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
static BankAccountType _stringToType(String? value) {
|
||||
if (value == null) return BankAccountType.checking;
|
||||
try {
|
||||
// Assuming backend enum names match or are uppercase
|
||||
return BankAccountType.values.firstWhere(
|
||||
(e) => e.name.toLowerCase() == value.toLowerCase(),
|
||||
orElse: () => BankAccountType.other,
|
||||
);
|
||||
} catch (_) {
|
||||
return BankAccountType.other;
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts domain type to string for backend.
|
||||
static String typeToString(BankAccountType type) {
|
||||
switch (type) {
|
||||
case BankAccountType.checking:
|
||||
return 'CHECKING';
|
||||
case BankAccountType.savings:
|
||||
return 'SAVINGS';
|
||||
default:
|
||||
return 'CHECKING';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import '../../entities/profile/emergency_contact.dart';
|
||||
|
||||
/// Adapter for [EmergencyContact] to map data layer values to domain entity.
|
||||
class EmergencyContactAdapter {
|
||||
/// Maps primitive values to [EmergencyContact].
|
||||
static EmergencyContact fromPrimitives({
|
||||
required String id,
|
||||
required String name,
|
||||
required String phone,
|
||||
String? relationship,
|
||||
}) {
|
||||
return EmergencyContact(
|
||||
id: id,
|
||||
name: name,
|
||||
phone: phone,
|
||||
relationship: EmergencyContact.stringToRelationshipType(relationship),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/// Adapter for Experience data (skills/industries) to map data layer values to domain models.
|
||||
class ExperienceAdapter {
|
||||
/// Converts a dynamic list (from backend AnyValue) to List<String>.
|
||||
///
|
||||
/// Handles nulls and converts elements to Strings.
|
||||
static List<String> fromDynamicList(dynamic data) {
|
||||
if (data == null) return <String>[];
|
||||
|
||||
if (data is List) {
|
||||
return data
|
||||
.where((dynamic e) => e != null)
|
||||
.map((dynamic e) => e.toString())
|
||||
.toList();
|
||||
}
|
||||
|
||||
return <String>[];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'relationship_type.dart';
|
||||
|
||||
/// Represents an emergency contact for a user.
|
||||
///
|
||||
@@ -6,19 +7,69 @@ import 'package:equatable/equatable.dart';
|
||||
class EmergencyContact extends Equatable {
|
||||
|
||||
const EmergencyContact({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.relationship,
|
||||
required this.phone,
|
||||
});
|
||||
|
||||
/// Unique identifier.
|
||||
final String id;
|
||||
|
||||
/// Full name of the contact.
|
||||
final String name;
|
||||
|
||||
/// Relationship to the user (e.g. "Spouse", "Parent").
|
||||
final String relationship;
|
||||
final RelationshipType relationship;
|
||||
|
||||
/// Phone number.
|
||||
final String phone;
|
||||
|
||||
@override
|
||||
List<Object?> get props => <Object?>[name, relationship, phone];
|
||||
}
|
||||
List<Object?> get props => <Object?>[id, name, relationship, phone];
|
||||
|
||||
/// Returns a copy of this [EmergencyContact] with the given fields replaced.
|
||||
EmergencyContact copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? phone,
|
||||
RelationshipType? relationship,
|
||||
}) {
|
||||
return EmergencyContact(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
phone: phone ?? this.phone,
|
||||
relationship: relationship ?? this.relationship,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns an empty [EmergencyContact].
|
||||
static EmergencyContact empty() {
|
||||
return const EmergencyContact(
|
||||
id: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
relationship: RelationshipType.family,
|
||||
);
|
||||
}
|
||||
|
||||
/// Converts a string value to a [RelationshipType].
|
||||
static RelationshipType stringToRelationshipType(String? value) {
|
||||
if (value != null) {
|
||||
final String strVal = value.toUpperCase();
|
||||
switch (strVal) {
|
||||
case 'FAMILY':
|
||||
return RelationshipType.family;
|
||||
case 'SPOUSE':
|
||||
return RelationshipType.spouse;
|
||||
case 'FRIEND':
|
||||
return RelationshipType.friend;
|
||||
case 'OTHER':
|
||||
return RelationshipType.other;
|
||||
default:
|
||||
return RelationshipType.other;
|
||||
}
|
||||
}
|
||||
return RelationshipType.other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
enum ExperienceSkill {
|
||||
foodService('food_service'),
|
||||
bartending('bartending'),
|
||||
eventSetup('event_setup'),
|
||||
hospitality('hospitality'),
|
||||
warehouse('warehouse'),
|
||||
customerService('customer_service'),
|
||||
cleaning('cleaning'),
|
||||
security('security'),
|
||||
retail('retail'),
|
||||
cooking('cooking'),
|
||||
cashier('cashier'),
|
||||
server('server'),
|
||||
barista('barista'),
|
||||
hostHostess('host_hostess'),
|
||||
busser('busser'),
|
||||
driving('driving');
|
||||
|
||||
final String value;
|
||||
const ExperienceSkill(this.value);
|
||||
|
||||
static ExperienceSkill? fromString(String value) {
|
||||
try {
|
||||
return ExperienceSkill.values.firstWhere((e) => e.value == value);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
enum Industry {
|
||||
hospitality('hospitality'),
|
||||
foodService('food_service'),
|
||||
warehouse('warehouse'),
|
||||
events('events'),
|
||||
retail('retail'),
|
||||
healthcare('healthcare'),
|
||||
other('other');
|
||||
|
||||
final String value;
|
||||
const Industry(this.value);
|
||||
|
||||
static Industry? fromString(String value) {
|
||||
try {
|
||||
return Industry.values.firstWhere((e) => e.value == value);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
enum RelationshipType {
|
||||
family,
|
||||
spouse,
|
||||
friend,
|
||||
other,
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import 'profile_setup_event.dart';
|
||||
import 'profile_setup_state.dart';
|
||||
|
||||
@@ -104,13 +104,13 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
.bio(state.bio.isEmpty ? null : state.bio)
|
||||
.preferredLocations(state.preferredLocations)
|
||||
.maxDistanceMiles(state.maxDistanceMiles.toInt())
|
||||
.industries(fdc.AnyValue(state.industries))
|
||||
.skills(fdc.AnyValue(state.skills))
|
||||
.industries(state.industries)
|
||||
.skills(state.skills)
|
||||
.email(email.isEmpty ? null : email)
|
||||
.phone(phone)
|
||||
.execute();
|
||||
|
||||
final String staffId = result.data?.staff_insert.id ?? '';
|
||||
final String staffId = result.data.staff_insert.id ;
|
||||
final Staff staff = Staff(
|
||||
id: staffId,
|
||||
authProviderId: firebaseUser.uid,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart';
|
||||
import 'package:staff_authentication/staff_authentication.dart';
|
||||
|
||||
@@ -17,28 +18,6 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
/// Callback for when industries change.
|
||||
final ValueChanged<List<String>> onIndustriesChanged;
|
||||
|
||||
static const List<String> _allSkillKeys = <String>[
|
||||
'food_service',
|
||||
'bartending',
|
||||
'warehouse',
|
||||
'retail',
|
||||
'events',
|
||||
'customer_service',
|
||||
'cleaning',
|
||||
'security',
|
||||
'driving',
|
||||
'cooking',
|
||||
];
|
||||
|
||||
static const List<String> _allIndustryKeys = <String>[
|
||||
'hospitality',
|
||||
'food_service',
|
||||
'warehouse',
|
||||
'events',
|
||||
'retail',
|
||||
'healthcare',
|
||||
];
|
||||
|
||||
/// Creates a [ProfileSetupExperience] widget.
|
||||
const ProfileSetupExperience({
|
||||
super.key,
|
||||
@@ -92,15 +71,15 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: _allSkillKeys.map((String key) {
|
||||
final bool isSelected = skills.contains(key);
|
||||
children: ExperienceSkill.values.map((ExperienceSkill skill) {
|
||||
final bool isSelected = skills.contains(skill.value);
|
||||
// Dynamic translation access
|
||||
final String label = _getSkillLabel(key);
|
||||
final String label = _getSkillLabel(skill);
|
||||
|
||||
return UiChip(
|
||||
label: label,
|
||||
isSelected: isSelected,
|
||||
onTap: () => _toggleSkill(skill: key),
|
||||
onTap: () => _toggleSkill(skill: skill.value),
|
||||
leadingIcon: isSelected ? UiIcons.check : null,
|
||||
variant: UiChipVariant.primary,
|
||||
);
|
||||
@@ -118,14 +97,14 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: _allIndustryKeys.map((String key) {
|
||||
final bool isSelected = industries.contains(key);
|
||||
final String label = _getIndustryLabel(key);
|
||||
children: Industry.values.map((Industry industry) {
|
||||
final bool isSelected = industries.contains(industry.value);
|
||||
final String label = _getIndustryLabel(industry);
|
||||
|
||||
return UiChip(
|
||||
label: label,
|
||||
isSelected: isSelected,
|
||||
onTap: () => _toggleIndustry(industry: key),
|
||||
onTap: () => _toggleIndustry(industry: industry.value),
|
||||
leadingIcon: isSelected ? UiIcons.check : null,
|
||||
variant: isSelected
|
||||
? UiChipVariant.accent
|
||||
@@ -137,72 +116,74 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
String _getSkillLabel(String key) {
|
||||
switch (key) {
|
||||
case 'food_service':
|
||||
String _getSkillLabel(ExperienceSkill skill) {
|
||||
switch (skill) {
|
||||
case ExperienceSkill.foodService:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.food_service;
|
||||
case 'bartending':
|
||||
case ExperienceSkill.bartending:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.bartending;
|
||||
case 'warehouse':
|
||||
case ExperienceSkill.warehouse:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.warehouse;
|
||||
case 'retail':
|
||||
case ExperienceSkill.retail:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.retail;
|
||||
case 'events':
|
||||
// Note: 'events' was removed from enum in favor of 'event_setup' or industry.
|
||||
// Using 'events' translation for eventSetup if available or fallback.
|
||||
case ExperienceSkill.eventSetup:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.events;
|
||||
case 'customer_service':
|
||||
case ExperienceSkill.customerService:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.customer_service;
|
||||
case 'cleaning':
|
||||
case ExperienceSkill.cleaning:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.cleaning;
|
||||
case 'security':
|
||||
case ExperienceSkill.security:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.security;
|
||||
case 'driving':
|
||||
case ExperienceSkill.driving:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.driving;
|
||||
case 'cooking':
|
||||
case ExperienceSkill.cooking:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
@@ -210,48 +191,48 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
.skills
|
||||
.cooking;
|
||||
default:
|
||||
return key;
|
||||
return skill.value;
|
||||
}
|
||||
}
|
||||
|
||||
String _getIndustryLabel(String key) {
|
||||
switch (key) {
|
||||
case 'hospitality':
|
||||
String _getIndustryLabel(Industry industry) {
|
||||
switch (industry) {
|
||||
case Industry.hospitality:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.hospitality;
|
||||
case 'food_service':
|
||||
case Industry.foodService:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.food_service;
|
||||
case 'warehouse':
|
||||
case Industry.warehouse:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.warehouse;
|
||||
case 'events':
|
||||
case Industry.events:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.events;
|
||||
case 'retail':
|
||||
case Industry.retail:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.retail;
|
||||
case 'healthcare':
|
||||
case Industry.healthcare:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
@@ -259,7 +240,7 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
.industries
|
||||
.healthcare;
|
||||
default:
|
||||
return key;
|
||||
return industry.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,58 +2,47 @@ import 'package:firebase_auth/firebase_auth.dart' as auth;
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../../domain/repositories/bank_account_repository.dart';
|
||||
|
||||
/// Implementation of [BankAccountRepository].
|
||||
/// Implementation of [BankAccountRepository] that integrates with Data Connect.
|
||||
class BankAccountRepositoryImpl implements BankAccountRepository {
|
||||
/// Creates a [BankAccountRepositoryImpl].
|
||||
const BankAccountRepositoryImpl({
|
||||
required this.dataConnect,
|
||||
required this.firebaseAuth,
|
||||
});
|
||||
|
||||
/// The Data Connect instance.
|
||||
final ExampleConnector dataConnect;
|
||||
/// The Firebase Auth instance.
|
||||
final auth.FirebaseAuth firebaseAuth;
|
||||
|
||||
@override
|
||||
Future<List<BankAccount>> getAccounts() async {
|
||||
final auth.User? user = firebaseAuth.currentUser;
|
||||
if (user == null) throw Exception('User not authenticated');
|
||||
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
|
||||
if (staffId == null || staffId.isEmpty) {
|
||||
print('BankAccount getAccounts: missing staffId userId=${user.uid} session=${StaffSessionStore.instance.session}');
|
||||
throw Exception('Staff profile is missing.');
|
||||
}
|
||||
|
||||
final String staffId = _getStaffId();
|
||||
|
||||
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
||||
result = await dataConnect
|
||||
.getAccountsByOwnerId(ownerId: staffId)
|
||||
.execute();
|
||||
|
||||
return result.data.accounts.map((GetAccountsByOwnerIdAccounts account) {
|
||||
return BankAccount(
|
||||
return BankAccountAdapter.fromPrimitives(
|
||||
id: account.id,
|
||||
userId: account.ownerId,
|
||||
bankName: account.bank,
|
||||
accountNumber: account.accountNumber ?? '',
|
||||
accountNumber: account.accountNumber,
|
||||
last4: account.last4,
|
||||
accountName: '', // Not returned by API
|
||||
sortCode: account.routeNumber,
|
||||
type: _mapAccountType(account.type),
|
||||
isPrimary: account.isPrimary ?? false,
|
||||
type: account.type is Known<AccountType> ? (account.type as Known<AccountType>).value.name : null,
|
||||
isPrimary: account.isPrimary,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> addAccount(BankAccount account) async {
|
||||
final auth.User? user = firebaseAuth.currentUser;
|
||||
if (user == null) throw Exception('User not authenticated');
|
||||
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
|
||||
if (staffId == null || staffId.isEmpty) {
|
||||
print('BankAccount addAccount: missing staffId userId=${user.uid} session=${StaffSessionStore.instance.session}');
|
||||
throw Exception('Staff profile is missing.');
|
||||
}
|
||||
final String staffId = _getStaffId();
|
||||
|
||||
final QueryResult<GetAccountsByOwnerIdData, GetAccountsByOwnerIdVariables>
|
||||
existingAccounts = await dataConnect
|
||||
@@ -64,44 +53,41 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
|
||||
|
||||
await dataConnect.createAccount(
|
||||
bank: account.bankName,
|
||||
type: _mapDomainType(account.type),
|
||||
type: AccountType.values.byName(BankAccountAdapter.typeToString(account.type)),
|
||||
last4: _safeLast4(account.last4, account.accountNumber),
|
||||
ownerId: staffId,
|
||||
).isPrimary(isPrimary).accountNumber(account.accountNumber).routeNumber(account.sortCode).execute();
|
||||
)
|
||||
.isPrimary(isPrimary)
|
||||
.accountNumber(account.accountNumber)
|
||||
.routeNumber(account.sortCode)
|
||||
.execute();
|
||||
}
|
||||
|
||||
BankAccountType _mapAccountType(EnumValue<AccountType> type) {
|
||||
if (type is Known<AccountType>) {
|
||||
switch (type.value) {
|
||||
case AccountType.CHECKING:
|
||||
return BankAccountType.checking;
|
||||
case AccountType.SAVINGS:
|
||||
return BankAccountType.savings;
|
||||
}
|
||||
/// Helper to get the logged-in staff ID.
|
||||
String _getStaffId() {
|
||||
final auth.User? user = firebaseAuth.currentUser;
|
||||
if (user == null) {
|
||||
throw Exception('User not authenticated');
|
||||
}
|
||||
return BankAccountType.other;
|
||||
}
|
||||
|
||||
AccountType _mapDomainType(BankAccountType type) {
|
||||
switch (type) {
|
||||
case BankAccountType.checking:
|
||||
return AccountType.CHECKING;
|
||||
case BankAccountType.savings:
|
||||
return AccountType.SAVINGS;
|
||||
default:
|
||||
return AccountType.CHECKING; // Default fallback
|
||||
|
||||
final String? staffId = StaffSessionStore.instance.session?.staff?.id;
|
||||
if (staffId == null || staffId.isEmpty) {
|
||||
throw Exception('Staff profile is missing or session not initialized.');
|
||||
}
|
||||
return staffId;
|
||||
}
|
||||
|
||||
/// Ensures we have a last4 value, either from input or derived from account number.
|
||||
String _safeLast4(String? last4, String accountNumber) {
|
||||
if (last4 != null && last4.isNotEmpty) {
|
||||
return last4;
|
||||
}
|
||||
if (accountNumber.isEmpty) {
|
||||
return '';
|
||||
return '0000';
|
||||
}
|
||||
return accountNumber.length > 4
|
||||
? accountNumber.substring(accountNumber.length - 4)
|
||||
: accountNumber;
|
||||
if (accountNumber.length < 4) {
|
||||
return accountNumber.padLeft(4, '0');
|
||||
}
|
||||
return accountNumber.substring(accountNumber.length - 4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/add_bank_account_params.dart';
|
||||
import '../../domain/usecases/add_bank_account_usecase.dart';
|
||||
@@ -20,7 +19,7 @@ class BankAccountCubit extends Cubit<BankAccountState> {
|
||||
Future<void> loadAccounts() async {
|
||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||
try {
|
||||
final accounts = await _getBankAccountsUseCase();
|
||||
final List<BankAccount> accounts = await _getBankAccountsUseCase();
|
||||
emit(state.copyWith(
|
||||
status: BankAccountStatus.loaded,
|
||||
accounts: accounts,
|
||||
@@ -45,7 +44,7 @@ class BankAccountCubit extends Cubit<BankAccountState> {
|
||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||
|
||||
// Create domain entity
|
||||
final newAccount = BankAccount(
|
||||
final BankAccount newAccount = BankAccount(
|
||||
id: '', // Generated by server usually
|
||||
userId: '', // Handled by Repo/Auth
|
||||
bankName: 'New Bank', // Mock
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Extensions for [EmergencyContact] to support UI operations.
|
||||
extension EmergencyContactExtensions on EmergencyContact {
|
||||
/// returns a copy of this [EmergencyContact] with the given fields replaced.
|
||||
EmergencyContact copyWith({
|
||||
String? name,
|
||||
String? phone,
|
||||
String? relationship,
|
||||
}) {
|
||||
return EmergencyContact(
|
||||
name: name ?? this.name,
|
||||
phone: phone ?? this.phone,
|
||||
relationship: relationship ?? this.relationship,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns an empty [EmergencyContact].
|
||||
static EmergencyContact empty() {
|
||||
return const EmergencyContact(
|
||||
name: '',
|
||||
phone: '',
|
||||
relationship: '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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +1,34 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/get_emergency_contacts_arguments.dart';
|
||||
import '../../domain/arguments/save_emergency_contacts_arguments.dart';
|
||||
import '../../domain/usecases/get_emergency_contacts_usecase.dart';
|
||||
import '../../domain/usecases/save_emergency_contacts_usecase.dart';
|
||||
import 'emergency_contact_event.dart';
|
||||
import 'emergency_contact_state.dart';
|
||||
|
||||
// Events
|
||||
abstract class EmergencyContactEvent extends Equatable {
|
||||
const EmergencyContactEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class EmergencyContactsLoaded extends EmergencyContactEvent {}
|
||||
|
||||
class EmergencyContactAdded extends EmergencyContactEvent {}
|
||||
|
||||
class EmergencyContactRemoved extends EmergencyContactEvent {
|
||||
final int index;
|
||||
|
||||
const EmergencyContactRemoved(this.index);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [index];
|
||||
}
|
||||
|
||||
class EmergencyContactUpdated extends EmergencyContactEvent {
|
||||
final int index;
|
||||
final EmergencyContact contact;
|
||||
|
||||
const EmergencyContactUpdated(this.index, this.contact);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [index, contact];
|
||||
}
|
||||
|
||||
class EmergencyContactsSaved extends EmergencyContactEvent {}
|
||||
|
||||
// State
|
||||
enum EmergencyContactStatus { initial, loading, success, saving, failure }
|
||||
|
||||
class EmergencyContactState extends Equatable {
|
||||
final EmergencyContactStatus status;
|
||||
final List<EmergencyContact> contacts;
|
||||
final String? errorMessage;
|
||||
|
||||
const EmergencyContactState({
|
||||
this.status = EmergencyContactStatus.initial,
|
||||
this.contacts = const [],
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
EmergencyContactState copyWith({
|
||||
EmergencyContactStatus? status,
|
||||
List<EmergencyContact>? contacts,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return EmergencyContactState(
|
||||
status: status ?? this.status,
|
||||
contacts: contacts ?? this.contacts,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isValid {
|
||||
if (contacts.isEmpty) return false;
|
||||
// Check if at least one contact is valid (or all?)
|
||||
// Usually all added contacts should be valid.
|
||||
return contacts.every((c) => c.name.isNotEmpty && c.phone.isNotEmpty);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, contacts, errorMessage];
|
||||
}
|
||||
export 'emergency_contact_event.dart';
|
||||
export 'emergency_contact_state.dart';
|
||||
|
||||
// BLoC
|
||||
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);
|
||||
on<EmergencyContactRemoved>(_onRemoved);
|
||||
on<EmergencyContactUpdated>(_onUpdated);
|
||||
on<EmergencyContactsSaved>(_onSaved);
|
||||
|
||||
add(EmergencyContactsLoaded());
|
||||
}
|
||||
|
||||
|
||||
Future<void> _onLoaded(
|
||||
EmergencyContactsLoaded event,
|
||||
@@ -102,13 +37,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,
|
||||
status: EmergencyContactStatus.loaded,
|
||||
contacts: contacts.isNotEmpty
|
||||
? contacts
|
||||
: [const EmergencyContact(name: '', phone: '', relationship: 'family')],
|
||||
: [EmergencyContact.empty()],
|
||||
));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
@@ -123,7 +58,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,11 +88,10 @@ class EmergencyContactBloc
|
||||
try {
|
||||
await saveEmergencyContacts(
|
||||
SaveEmergencyContactsArguments(
|
||||
staffId: staffId,
|
||||
contacts: state.contacts,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: EmergencyContactStatus.success));
|
||||
emit(state.copyWith(status: EmergencyContactStatus.saved));
|
||||
} catch (e) {
|
||||
emit(state.copyWith(
|
||||
status: EmergencyContactStatus.failure,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
abstract class EmergencyContactEvent extends Equatable {
|
||||
const EmergencyContactEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class EmergencyContactsLoaded extends EmergencyContactEvent {}
|
||||
|
||||
class EmergencyContactAdded extends EmergencyContactEvent {}
|
||||
|
||||
class EmergencyContactRemoved extends EmergencyContactEvent {
|
||||
final int index;
|
||||
|
||||
const EmergencyContactRemoved(this.index);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [index];
|
||||
}
|
||||
|
||||
class EmergencyContactUpdated extends EmergencyContactEvent {
|
||||
final int index;
|
||||
final EmergencyContact contact;
|
||||
|
||||
const EmergencyContactUpdated(this.index, this.contact);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [index, contact];
|
||||
}
|
||||
|
||||
class EmergencyContactsSaved extends EmergencyContactEvent {}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
enum EmergencyContactStatus { initial, loading, loaded, saving, saved, failure }
|
||||
|
||||
class EmergencyContactState extends Equatable {
|
||||
final EmergencyContactStatus status;
|
||||
final List<EmergencyContact> contacts;
|
||||
final String? errorMessage;
|
||||
|
||||
const EmergencyContactState({
|
||||
this.status = EmergencyContactStatus.initial,
|
||||
this.contacts = const [],
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
EmergencyContactState copyWith({
|
||||
EmergencyContactStatus? status,
|
||||
List<EmergencyContact>? contacts,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return EmergencyContactState(
|
||||
status: status ?? this.status,
|
||||
contacts: contacts ?? this.contacts,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isValid {
|
||||
if (contacts.isEmpty) return false;
|
||||
// Check if at least one contact is valid (or all?)
|
||||
// Usually all added contacts should be valid.
|
||||
return contacts.every((c) => c.name.isNotEmpty && c.phone.isNotEmpty);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, contacts, errorMessage];
|
||||
}
|
||||
@@ -17,19 +17,6 @@ import '../widgets/emergency_contact_save_button.dart';
|
||||
class EmergencyContactScreen extends StatelessWidget {
|
||||
const EmergencyContactScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) =>
|
||||
Modular.get<EmergencyContactBloc>()..add(EmergencyContactsLoaded()),
|
||||
child: const _EmergencyContactView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmergencyContactView extends StatelessWidget {
|
||||
const _EmergencyContactView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -48,44 +35,47 @@ class _EmergencyContactView extends StatelessWidget {
|
||||
child: Container(color: UiColors.border, height: 1.0),
|
||||
),
|
||||
),
|
||||
body: BlocConsumer<EmergencyContactBloc, EmergencyContactState>(
|
||||
listener: (context, state) {
|
||||
if (state.status == EmergencyContactStatus.failure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.errorMessage ?? 'An error occurred')),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state.status == EmergencyContactStatus.loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(UiConstants.space6),
|
||||
child: Column(
|
||||
children: [
|
||||
const EmergencyContactInfoBanner(),
|
||||
SizedBox(height: UiConstants.space6),
|
||||
...state.contacts.asMap().entries.map(
|
||||
(entry) => EmergencyContactFormItem(
|
||||
index: entry.key,
|
||||
contact: entry.value,
|
||||
totalContacts: state.contacts.length,
|
||||
body: BlocProvider(
|
||||
create: (context) => Modular.get<EmergencyContactBloc>(),
|
||||
child: BlocConsumer<EmergencyContactBloc, EmergencyContactState>(
|
||||
listener: (context, state) {
|
||||
if (state.status == EmergencyContactStatus.failure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.errorMessage ?? 'An error occurred')),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state.status == EmergencyContactStatus.loading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(UiConstants.space6),
|
||||
child: Column(
|
||||
children: [
|
||||
const EmergencyContactInfoBanner(),
|
||||
SizedBox(height: UiConstants.space6),
|
||||
...state.contacts.asMap().entries.map(
|
||||
(entry) => EmergencyContactFormItem(
|
||||
index: entry.key,
|
||||
contact: entry.value,
|
||||
totalContacts: state.contacts.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
const EmergencyContactAddButton(),
|
||||
SizedBox(height: UiConstants.space16),
|
||||
],
|
||||
const EmergencyContactAddButton(),
|
||||
SizedBox(height: UiConstants.space16),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
EmergencyContactSaveButton(state: state),
|
||||
],
|
||||
);
|
||||
},
|
||||
const EmergencyContactSaveButton(),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,54 +4,59 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../blocs/emergency_contact_bloc.dart';
|
||||
|
||||
class EmergencyContactSaveButton extends StatelessWidget {
|
||||
final EmergencyContactState state;
|
||||
const EmergencyContactSaveButton({super.key});
|
||||
|
||||
const EmergencyContactSaveButton({super.key, required this.state});
|
||||
void _onSave(BuildContext context) {
|
||||
context.read<EmergencyContactBloc>().add(EmergencyContactsSaved());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
border: Border(top: BorderSide(color: UiColors.border)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: state.isValid
|
||||
? () => context
|
||||
.read<EmergencyContactBloc>()
|
||||
.add(EmergencyContactsSaved())
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: UiColors.primary,
|
||||
foregroundColor: UiColors.primaryForeground,
|
||||
disabledBackgroundColor: UiColors.textPlaceholder,
|
||||
padding: EdgeInsets.symmetric(vertical: UiConstants.space4),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
return BlocConsumer<EmergencyContactBloc, EmergencyContactState>(
|
||||
listenWhen: (previous, current) => previous.status != current.status,
|
||||
listener: (context, state) {
|
||||
if (state.status == EmergencyContactStatus.saved) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Emergency contacts saved successfully',
|
||||
style: UiTypography.body2r.textPrimary,
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: UiColors.iconSuccess,
|
||||
),
|
||||
child: state.status == EmergencyContactStatus.saving
|
||||
? SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(UiColors.primaryForeground),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
'Save & Continue',
|
||||
style: UiTypography.title2b,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
final isLoading = state.status == EmergencyContactStatus.saving;
|
||||
return Container(
|
||||
padding: EdgeInsets.all(UiConstants.space4),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.bgPopup,
|
||||
border: Border(top: BorderSide(color: UiColors.border)),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: UiButton.primary(
|
||||
fullWidth: true,
|
||||
onPressed: state.isValid && !isLoading
|
||||
? () => _onSave(context)
|
||||
: null,
|
||||
child: isLoading
|
||||
? SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
UiColors.primaryForeground,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Text('Save & Continue'),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -26,11 +28,10 @@ class StaffEmergencyContactModule extends Module {
|
||||
);
|
||||
|
||||
// BLoC
|
||||
i.addLazySingleton<EmergencyContactBloc>(
|
||||
i.add<EmergencyContactBloc>(
|
||||
() => EmergencyContactBloc(
|
||||
getEmergencyContacts: i.get<GetEmergencyContactsUseCase>(),
|
||||
saveEmergencyContacts: i.get<SaveEmergencyContactsUseCase>(),
|
||||
staffId: 'mock-staff-id', // TODO: Get direct from auth state
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
# include: package:flutter_lints/flutter.yaml
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
@@ -1,29 +1,58 @@
|
||||
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 '../../domain/repositories/experience_repository_interface.dart';
|
||||
|
||||
/// Implementation of [ExperienceRepositoryInterface] that delegates to Data Connect.
|
||||
class ExperienceRepositoryImpl implements ExperienceRepositoryInterface {
|
||||
final ProfileRepositoryMock _mockRepository;
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
// ignore: unused_field
|
||||
final FirebaseAuth _firebaseAuth;
|
||||
|
||||
/// Creates a [ExperienceRepositoryImpl] with the given [ProfileRepositoryMock].
|
||||
ExperienceRepositoryImpl(this._mockRepository);
|
||||
/// Creates a [ExperienceRepositoryImpl] using Da a Connect and Auth.
|
||||
ExperienceRepositoryImpl({
|
||||
required dc.ExampleConnector dataConnect,
|
||||
required FirebaseAuth firebaseAuth,
|
||||
}) : _dataConnect = dataConnect,
|
||||
_firebaseAuth = firebaseAuth;
|
||||
|
||||
@override
|
||||
Future<List<String>> getIndustries(String staffId) {
|
||||
return _mockRepository.getStaffIndustries(staffId);
|
||||
Future<dc.GetStaffByUserIdStaffs> _getStaff() 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;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getSkills(String staffId) {
|
||||
return _mockRepository.getStaffSkills(staffId);
|
||||
Future<List<String>> getIndustries() async {
|
||||
final staff = await _getStaff();
|
||||
return staff.industries ?? [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getSkills() async {
|
||||
final staff = await _getStaff();
|
||||
return staff.skills ?? [];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveExperience(
|
||||
String staffId,
|
||||
List<String> industries,
|
||||
List<String> skills,
|
||||
) {
|
||||
return _mockRepository.saveExperience(staffId, industries, skills);
|
||||
) async {
|
||||
try {
|
||||
final staff = await _getStaff();
|
||||
await _dataConnect
|
||||
.updateStaff(id: staff.id)
|
||||
.industries(industries)
|
||||
.skills(skills)
|
||||
.execute();
|
||||
} catch (e) {
|
||||
throw Exception('Failed to save experience: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
class GetExperienceArguments extends UseCaseArgument {
|
||||
final String staffId;
|
||||
|
||||
GetExperienceArguments({required this.staffId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [staffId];
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
class SaveExperienceArguments extends UseCaseArgument {
|
||||
final String staffId;
|
||||
final List<String> industries;
|
||||
final List<String> skills;
|
||||
|
||||
SaveExperienceArguments({
|
||||
required this.staffId,
|
||||
required this.industries,
|
||||
required this.skills,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [staffId, industries, skills];
|
||||
List<Object?> get props => [industries, skills];
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/// Interface for accessing staff experience data.
|
||||
abstract class ExperienceRepositoryInterface {
|
||||
/// Fetches the list of industries associated with the staff member.
|
||||
Future<List<String>> getIndustries(String staffId);
|
||||
Future<List<String>> getIndustries();
|
||||
|
||||
/// Fetches the list of skills associated with the staff member.
|
||||
Future<List<String>> getSkills(String staffId);
|
||||
Future<List<String>> getSkills();
|
||||
|
||||
/// Saves the staff member's experience (industries and skills).
|
||||
Future<void> saveExperience(
|
||||
String staffId,
|
||||
List<String> industries,
|
||||
List<String> skills,
|
||||
);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../arguments/get_experience_arguments.dart';
|
||||
import '../repositories/experience_repository_interface.dart';
|
||||
|
||||
/// Use case for fetching staff industries.
|
||||
class GetStaffIndustriesUseCase implements UseCase<GetExperienceArguments, List<String>> {
|
||||
class GetStaffIndustriesUseCase implements NoInputUseCase<List<String>> {
|
||||
final ExperienceRepositoryInterface _repository;
|
||||
|
||||
GetStaffIndustriesUseCase(this._repository);
|
||||
|
||||
@override
|
||||
Future<List<String>> call(GetExperienceArguments input) {
|
||||
return _repository.getIndustries(input.staffId);
|
||||
Future<List<String>> call() {
|
||||
return _repository.getIndustries();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../arguments/get_experience_arguments.dart';
|
||||
import '../repositories/experience_repository_interface.dart';
|
||||
|
||||
/// Use case for fetching staff skills.
|
||||
class GetStaffSkillsUseCase implements UseCase<GetExperienceArguments, List<String>> {
|
||||
class GetStaffSkillsUseCase implements NoInputUseCase<List<String>> {
|
||||
final ExperienceRepositoryInterface _repository;
|
||||
|
||||
GetStaffSkillsUseCase(this._repository);
|
||||
|
||||
@override
|
||||
Future<List<String>> call(GetExperienceArguments input) {
|
||||
return _repository.getSkills(input.staffId);
|
||||
Future<List<String>> call() {
|
||||
return _repository.getSkills();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ class SaveExperienceUseCase extends UseCase<SaveExperienceArguments, void> {
|
||||
@override
|
||||
Future<void> call(SaveExperienceArguments params) {
|
||||
return repository.saveExperience(
|
||||
params.staffId,
|
||||
params.industries,
|
||||
params.skills,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../domain/arguments/get_experience_arguments.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/save_experience_arguments.dart';
|
||||
import '../../domain/usecases/get_staff_industries_usecase.dart';
|
||||
import '../../domain/usecases/get_staff_skills_usecase.dart';
|
||||
@@ -17,7 +17,7 @@ abstract class ExperienceEvent extends Equatable {
|
||||
class ExperienceLoaded extends ExperienceEvent {}
|
||||
|
||||
class ExperienceIndustryToggled extends ExperienceEvent {
|
||||
final String industry;
|
||||
final Industry industry;
|
||||
const ExperienceIndustryToggled(this.industry);
|
||||
|
||||
@override
|
||||
@@ -47,10 +47,10 @@ enum ExperienceStatus { initial, loading, success, failure }
|
||||
|
||||
class ExperienceState extends Equatable {
|
||||
final ExperienceStatus status;
|
||||
final List<String> selectedIndustries;
|
||||
final List<Industry> selectedIndustries;
|
||||
final List<String> selectedSkills;
|
||||
final List<String> availableIndustries;
|
||||
final List<String> availableSkills;
|
||||
final List<Industry> availableIndustries;
|
||||
final List<ExperienceSkill> availableSkills;
|
||||
final String? errorMessage;
|
||||
|
||||
const ExperienceState({
|
||||
@@ -64,10 +64,10 @@ class ExperienceState extends Equatable {
|
||||
|
||||
ExperienceState copyWith({
|
||||
ExperienceStatus? status,
|
||||
List<String>? selectedIndustries,
|
||||
List<Industry>? selectedIndustries,
|
||||
List<String>? selectedSkills,
|
||||
List<String>? availableIndustries,
|
||||
List<String>? availableSkills,
|
||||
List<Industry>? availableIndustries,
|
||||
List<ExperienceSkill>? availableSkills,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return ExperienceState(
|
||||
@@ -93,53 +93,26 @@ class ExperienceState extends Equatable {
|
||||
|
||||
// BLoC
|
||||
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
static const List<String> _kAllIndustries = [
|
||||
'hospitality',
|
||||
'food_service',
|
||||
'warehouse',
|
||||
'events',
|
||||
'retail',
|
||||
'healthcare',
|
||||
'other',
|
||||
];
|
||||
|
||||
static const List<String> _kAllSkills = [
|
||||
'food_service',
|
||||
'bartending',
|
||||
'event_setup',
|
||||
'hospitality',
|
||||
'warehouse',
|
||||
'customer_service',
|
||||
'cleaning',
|
||||
'security',
|
||||
'retail',
|
||||
'cooking',
|
||||
'cashier',
|
||||
'server',
|
||||
'barista',
|
||||
'host_hostess',
|
||||
'busser',
|
||||
];
|
||||
|
||||
final GetStaffIndustriesUseCase getIndustries;
|
||||
final GetStaffSkillsUseCase getSkills;
|
||||
final SaveExperienceUseCase saveExperience;
|
||||
final String staffId;
|
||||
|
||||
ExperienceBloc({
|
||||
required this.getIndustries,
|
||||
required this.getSkills,
|
||||
required this.saveExperience,
|
||||
required this.staffId,
|
||||
}) : super(const ExperienceState(
|
||||
availableIndustries: _kAllIndustries,
|
||||
availableSkills: _kAllSkills,
|
||||
availableIndustries: Industry.values,
|
||||
availableSkills: ExperienceSkill.values,
|
||||
)) {
|
||||
on<ExperienceLoaded>(_onLoaded);
|
||||
on<ExperienceIndustryToggled>(_onIndustryToggled);
|
||||
on<ExperienceSkillToggled>(_onSkillToggled);
|
||||
on<ExperienceCustomSkillAdded>(_onCustomSkillAdded);
|
||||
on<ExperienceSubmitted>(_onSubmitted);
|
||||
|
||||
add(ExperienceLoaded());
|
||||
}
|
||||
|
||||
Future<void> _onLoaded(
|
||||
@@ -148,15 +121,17 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
) async {
|
||||
emit(state.copyWith(status: ExperienceStatus.loading));
|
||||
try {
|
||||
final arguments = GetExperienceArguments(staffId: staffId);
|
||||
final results = await Future.wait([
|
||||
getIndustries(arguments),
|
||||
getSkills(arguments),
|
||||
getIndustries(),
|
||||
getSkills(),
|
||||
]);
|
||||
|
||||
emit(state.copyWith(
|
||||
status: ExperienceStatus.initial,
|
||||
selectedIndustries: results[0],
|
||||
selectedIndustries: results[0]
|
||||
.map((e) => Industry.fromString(e))
|
||||
.whereType<Industry>()
|
||||
.toList(),
|
||||
selectedSkills: results[1],
|
||||
));
|
||||
} catch (e) {
|
||||
@@ -171,7 +146,7 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
ExperienceIndustryToggled event,
|
||||
Emitter<ExperienceState> emit,
|
||||
) {
|
||||
final industries = List<String>.from(state.selectedIndustries);
|
||||
final industries = List<Industry>.from(state.selectedIndustries);
|
||||
if (industries.contains(event.industry)) {
|
||||
industries.remove(event.industry);
|
||||
} else {
|
||||
@@ -211,8 +186,7 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState> {
|
||||
try {
|
||||
await saveExperience(
|
||||
SaveExperienceArguments(
|
||||
staffId: staffId,
|
||||
industries: state.selectedIndustries,
|
||||
industries: state.selectedIndustries.map((e) => e.value).toList(),
|
||||
skills: state.selectedSkills,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,56 +3,44 @@ import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
import '../blocs/experience_bloc.dart';
|
||||
import '../widgets/experience_custom_input.dart';
|
||||
import '../widgets/experience_section_title.dart';
|
||||
|
||||
class ExperiencePage extends StatelessWidget {
|
||||
const ExperiencePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => Modular.get<ExperienceBloc>()..add(ExperienceLoaded()),
|
||||
child: const _ExperienceView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ExperienceView extends StatelessWidget {
|
||||
const _ExperienceView();
|
||||
|
||||
String _getIndustryLabel(dynamic node, String key) {
|
||||
switch (key) {
|
||||
case 'hospitality': return node.hospitality;
|
||||
case 'food_service': return node.food_service;
|
||||
case 'warehouse': return node.warehouse;
|
||||
case 'events': return node.events;
|
||||
case 'retail': return node.retail;
|
||||
case 'healthcare': return node.healthcare;
|
||||
case 'other': return node.other;
|
||||
default: return key;
|
||||
String _getIndustryLabel(dynamic node, Industry industry) {
|
||||
switch (industry) {
|
||||
case Industry.hospitality: return node.hospitality;
|
||||
case Industry.foodService: return node.food_service;
|
||||
case Industry.warehouse: return node.warehouse;
|
||||
case Industry.events: return node.events;
|
||||
case Industry.retail: return node.retail;
|
||||
case Industry.healthcare: return node.healthcare;
|
||||
case Industry.other: return node.other;
|
||||
}
|
||||
}
|
||||
|
||||
String _getSkillLabel(dynamic node, String key) {
|
||||
switch (key) {
|
||||
case 'food_service': return node.food_service;
|
||||
case 'bartending': return node.bartending;
|
||||
case 'event_setup': return node.event_setup;
|
||||
case 'hospitality': return node.hospitality;
|
||||
case 'warehouse': return node.warehouse;
|
||||
case 'customer_service': return node.customer_service;
|
||||
case 'cleaning': return node.cleaning;
|
||||
case 'security': return node.security;
|
||||
case 'retail': return node.retail;
|
||||
case 'cooking': return node.cooking;
|
||||
case 'cashier': return node.cashier;
|
||||
case 'server': return node.server;
|
||||
case 'barista': return node.barista;
|
||||
case 'host_hostess': return node.host_hostess;
|
||||
case 'busser': return node.busser;
|
||||
default: return key;
|
||||
String _getSkillLabel(dynamic node, ExperienceSkill skill) {
|
||||
switch (skill) {
|
||||
case ExperienceSkill.foodService: return node.food_service;
|
||||
case ExperienceSkill.bartending: return node.bartending;
|
||||
case ExperienceSkill.eventSetup: return node.event_setup;
|
||||
case ExperienceSkill.hospitality: return node.hospitality;
|
||||
case ExperienceSkill.warehouse: return node.warehouse;
|
||||
case ExperienceSkill.customerService: return node.customer_service;
|
||||
case ExperienceSkill.cleaning: return node.cleaning;
|
||||
case ExperienceSkill.security: return node.security;
|
||||
case ExperienceSkill.retail: return node.retail;
|
||||
case ExperienceSkill.driving: return node.driving;
|
||||
case ExperienceSkill.cooking: return node.cooking;
|
||||
case ExperienceSkill.cashier: return node.cashier;
|
||||
case ExperienceSkill.server: return node.server;
|
||||
case ExperienceSkill.barista: return node.barista;
|
||||
case ExperienceSkill.hostHostess: return node.host_hostess;
|
||||
case ExperienceSkill.busser: return node.busser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,93 +49,97 @@ class _ExperienceView extends StatelessWidget {
|
||||
final i18n = t.staff.onboarding.experience;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: UiAppBar(
|
||||
title: i18n.title,
|
||||
onLeadingPressed: () => Modular.to.pop(),
|
||||
),
|
||||
body: BlocConsumer<ExperienceBloc, ExperienceState>(
|
||||
listener: (context, state) {
|
||||
body: BlocProvider<ExperienceBloc>(
|
||||
create: (context) => Modular.get<ExperienceBloc>(),
|
||||
child: BlocConsumer<ExperienceBloc, ExperienceState>(
|
||||
listener: (context, state) {
|
||||
if (state.status == ExperienceStatus.success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Experience saved successfully')),
|
||||
);
|
||||
Modular.to.pop();
|
||||
} else if (state.status == ExperienceStatus.failure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(state.errorMessage ?? 'An error occurred')),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ExperienceSectionTitle(title: i18n.industries_title),
|
||||
Text(
|
||||
i18n.industries_subtitle,
|
||||
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
|
||||
),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: state.availableIndustries
|
||||
.map(
|
||||
(i) => UiChip(
|
||||
label: _getIndustryLabel(i18n.industries, i),
|
||||
isSelected: state.selectedIndustries.contains(i),
|
||||
onTap: () => BlocProvider.of<ExperienceBloc>(context)
|
||||
.add(ExperienceIndustryToggled(i)),
|
||||
variant: state.selectedIndustries.contains(i)
|
||||
? UiChipVariant.primary
|
||||
: UiChipVariant.secondary,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space6),
|
||||
ExperienceSectionTitle(title: i18n.skills_title),
|
||||
Text(
|
||||
i18n.skills_subtitle,
|
||||
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
|
||||
),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: state.availableSkills
|
||||
.map(
|
||||
(s) => UiChip(
|
||||
label: _getSkillLabel(i18n.skills, s),
|
||||
isSelected: state.selectedSkills.contains(s),
|
||||
onTap: () => BlocProvider.of<ExperienceBloc>(context)
|
||||
.add(ExperienceSkillToggled(s)),
|
||||
variant: state.selectedSkills.contains(s)
|
||||
? UiChipVariant.primary
|
||||
: UiChipVariant.secondary,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space4),
|
||||
const ExperienceCustomInput(),
|
||||
SizedBox(height: UiConstants.space4),
|
||||
_buildCustomSkillsList(state, i18n),
|
||||
SizedBox(height: UiConstants.space10),
|
||||
],
|
||||
),
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ExperienceSectionTitle(title: i18n.industries_title),
|
||||
Text(
|
||||
i18n.industries_subtitle,
|
||||
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
|
||||
),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: state.availableIndustries
|
||||
.map(
|
||||
(i) => UiChip(
|
||||
label: _getIndustryLabel(i18n.industries, i),
|
||||
isSelected: state.selectedIndustries.contains(i),
|
||||
onTap: () => BlocProvider.of<ExperienceBloc>(context)
|
||||
.add(ExperienceIndustryToggled(i)),
|
||||
variant: state.selectedIndustries.contains(i)
|
||||
? UiChipVariant.primary
|
||||
: UiChipVariant.secondary,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
SizedBox(height: UiConstants.space6),
|
||||
ExperienceSectionTitle(title: i18n.skills_title),
|
||||
Text(
|
||||
i18n.skills_subtitle,
|
||||
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary),
|
||||
),
|
||||
SizedBox(height: UiConstants.space3),
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: state.availableSkills
|
||||
.map(
|
||||
(s) => UiChip(
|
||||
label: _getSkillLabel(i18n.skills, s),
|
||||
isSelected: state.selectedSkills.contains(s.value),
|
||||
onTap: () => BlocProvider.of<ExperienceBloc>(context)
|
||||
.add(ExperienceSkillToggled(s.value)),
|
||||
variant: state.selectedSkills.contains(s.value)
|
||||
? UiChipVariant.primary
|
||||
: UiChipVariant.secondary,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildSaveButton(context, state, i18n),
|
||||
],
|
||||
);
|
||||
},
|
||||
_buildSaveButton(context, state, i18n),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCustomSkillsList(ExperienceState state, dynamic i18n) {
|
||||
final customSkills = state.selectedSkills
|
||||
.where((s) => !state.availableSkills.contains(s))
|
||||
.where((s) => !state.availableSkills.any((e) => e.value == s))
|
||||
.toList();
|
||||
if (customSkills.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
@@ -181,25 +173,22 @@ class _ExperienceView extends StatelessWidget {
|
||||
border: Border(top: BorderSide(color: UiColors.border)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: UiButton.primary(
|
||||
onPressed: state.status == ExperienceStatus.loading
|
||||
? null
|
||||
: () => BlocProvider.of<ExperienceBloc>(context).add(ExperienceSubmitted()),
|
||||
fullWidth: true,
|
||||
text: state.status == ExperienceStatus.loading ? null : i18n.save_button,
|
||||
child: state.status == ExperienceStatus.loading
|
||||
? SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(UiColors.white), // UiColors.primaryForeground is white mostly
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: UiButton.primary(
|
||||
onPressed: state.status == ExperienceStatus.loading
|
||||
? null
|
||||
: () => BlocProvider.of<ExperienceBloc>(context).add(ExperienceSubmitted()),
|
||||
fullWidth: true,
|
||||
text: state.status == ExperienceStatus.loading ? null : i18n.save_button,
|
||||
child: state.status == ExperienceStatus.loading
|
||||
? SizedBox(
|
||||
height: 20.0,
|
||||
width: 20.0,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(UiColors.white), // UiColors.primaryForeground is white mostly
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
library staff_profile_experience;
|
||||
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
|
||||
@@ -21,7 +22,10 @@ class StaffProfileExperienceModule extends Module {
|
||||
void binds(Injector i) {
|
||||
// Repository
|
||||
i.addLazySingleton<ExperienceRepositoryInterface>(
|
||||
() => ExperienceRepositoryImpl(i.get<ProfileRepositoryMock>()),
|
||||
() => ExperienceRepositoryImpl(
|
||||
dataConnect: ExampleConnector.instance,
|
||||
firebaseAuth: FirebaseAuth.instance,
|
||||
),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
@@ -36,13 +40,11 @@ class StaffProfileExperienceModule extends Module {
|
||||
);
|
||||
|
||||
// BLoC
|
||||
i.addLazySingleton<ExperienceBloc>(
|
||||
i.add<ExperienceBloc>(
|
||||
() => ExperienceBloc(
|
||||
getIndustries: i.get<GetStaffIndustriesUseCase>(),
|
||||
getSkills: i.get<GetStaffSkillsUseCase>(),
|
||||
saveExperience: i.get<SaveExperienceUseCase>(),
|
||||
// TODO: Get actual logged in staff ID
|
||||
staffId: 'current-staff-id',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,15 +17,16 @@ dependencies:
|
||||
|
||||
# Architecture Packages
|
||||
krow_domain:
|
||||
path: ../../../../../../domain
|
||||
path: ../../../../../domain
|
||||
krow_core:
|
||||
path: ../../../../../../core
|
||||
path: ../../../../../core
|
||||
krow_data_connect:
|
||||
path: ../../../../../../data_connect
|
||||
path: ../../../../../data_connect
|
||||
firebase_auth: ^6.1.2
|
||||
design_system:
|
||||
path: ../../../../../../design_system
|
||||
path: ../../../../../design_system
|
||||
core_localization:
|
||||
path: ../../../../../../core_localization
|
||||
path: ../../../../../core_localization
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user