feat: migrate experience management to V2 API; add support for industries and skills
This commit is contained in:
@@ -75,6 +75,10 @@ abstract final class StaffEndpoints {
|
|||||||
/// Skills.
|
/// Skills.
|
||||||
static const ApiEndpoint skills = ApiEndpoint('/staff/profile/skills');
|
static const ApiEndpoint skills = ApiEndpoint('/staff/profile/skills');
|
||||||
|
|
||||||
|
/// Save/update experience (industries + skills).
|
||||||
|
static const ApiEndpoint experience =
|
||||||
|
ApiEndpoint('/staff/profile/experience');
|
||||||
|
|
||||||
/// Documents.
|
/// Documents.
|
||||||
static const ApiEndpoint documents =
|
static const ApiEndpoint documents =
|
||||||
ApiEndpoint('/staff/profile/documents');
|
ApiEndpoint('/staff/profile/documents');
|
||||||
|
|||||||
@@ -823,6 +823,8 @@
|
|||||||
"custom_skills_title": "Custom Skills:",
|
"custom_skills_title": "Custom Skills:",
|
||||||
"custom_skill_hint": "Add custom skill...",
|
"custom_skill_hint": "Add custom skill...",
|
||||||
"save_button": "Save & Continue",
|
"save_button": "Save & Continue",
|
||||||
|
"save_success": "Experience saved successfully",
|
||||||
|
"save_error": "An error occurred",
|
||||||
"industries": {
|
"industries": {
|
||||||
"hospitality": "Hospitality",
|
"hospitality": "Hospitality",
|
||||||
"food_service": "Food Service",
|
"food_service": "Food Service",
|
||||||
@@ -830,6 +832,8 @@
|
|||||||
"events": "Events",
|
"events": "Events",
|
||||||
"retail": "Retail",
|
"retail": "Retail",
|
||||||
"healthcare": "Healthcare",
|
"healthcare": "Healthcare",
|
||||||
|
"catering": "Catering",
|
||||||
|
"cafe": "Cafe",
|
||||||
"other": "Other"
|
"other": "Other"
|
||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
|
|||||||
@@ -818,6 +818,8 @@
|
|||||||
"custom_skills_title": "Habilidades personalizadas:",
|
"custom_skills_title": "Habilidades personalizadas:",
|
||||||
"custom_skill_hint": "A\u00f1adir habilidad...",
|
"custom_skill_hint": "A\u00f1adir habilidad...",
|
||||||
"save_button": "Guardar y continuar",
|
"save_button": "Guardar y continuar",
|
||||||
|
"save_success": "Experiencia guardada exitosamente",
|
||||||
|
"save_error": "Ocurrió un error",
|
||||||
"industries": {
|
"industries": {
|
||||||
"hospitality": "Hoteler\u00eda",
|
"hospitality": "Hoteler\u00eda",
|
||||||
"food_service": "Servicio de alimentos",
|
"food_service": "Servicio de alimentos",
|
||||||
@@ -825,6 +827,8 @@
|
|||||||
"events": "Eventos",
|
"events": "Eventos",
|
||||||
"retail": "Venta al por menor",
|
"retail": "Venta al por menor",
|
||||||
"healthcare": "Cuidado de la salud",
|
"healthcare": "Cuidado de la salud",
|
||||||
|
"catering": "Catering",
|
||||||
|
"cafe": "Cafetería",
|
||||||
"other": "Otro"
|
"other": "Otro"
|
||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export 'src/entities/enums/onboarding_status.dart';
|
|||||||
export 'src/entities/enums/order_type.dart';
|
export 'src/entities/enums/order_type.dart';
|
||||||
export 'src/entities/enums/payment_status.dart';
|
export 'src/entities/enums/payment_status.dart';
|
||||||
export 'src/entities/enums/shift_status.dart';
|
export 'src/entities/enums/shift_status.dart';
|
||||||
|
export 'src/entities/enums/staff_industry.dart';
|
||||||
|
export 'src/entities/enums/staff_skill.dart';
|
||||||
export 'src/entities/enums/staff_status.dart';
|
export 'src/entities/enums/staff_status.dart';
|
||||||
export 'src/entities/enums/user_role.dart';
|
export 'src/entities/enums/user_role.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/// Industry options for staff experience profiles.
|
||||||
|
///
|
||||||
|
/// Values match the V2 API format (UPPER_SNAKE_CASE).
|
||||||
|
enum StaffIndustry {
|
||||||
|
/// Hospitality industry.
|
||||||
|
hospitality('HOSPITALITY'),
|
||||||
|
|
||||||
|
/// Food service industry.
|
||||||
|
foodService('FOOD_SERVICE'),
|
||||||
|
|
||||||
|
/// Warehouse / logistics industry.
|
||||||
|
warehouse('WAREHOUSE'),
|
||||||
|
|
||||||
|
/// Events industry.
|
||||||
|
events('EVENTS'),
|
||||||
|
|
||||||
|
/// Retail industry.
|
||||||
|
retail('RETAIL'),
|
||||||
|
|
||||||
|
/// Healthcare industry.
|
||||||
|
healthcare('HEALTHCARE'),
|
||||||
|
|
||||||
|
/// Catering industry.
|
||||||
|
catering('CATERING'),
|
||||||
|
|
||||||
|
/// Cafe / coffee shop industry.
|
||||||
|
cafe('CAFE'),
|
||||||
|
|
||||||
|
/// Other / unspecified industry.
|
||||||
|
other('OTHER');
|
||||||
|
|
||||||
|
const StaffIndustry(this.value);
|
||||||
|
|
||||||
|
/// The V2 API string representation.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
/// Deserialises from a V2 API string with safe fallback.
|
||||||
|
static StaffIndustry? fromJson(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
for (final StaffIndustry industry in StaffIndustry.values) {
|
||||||
|
if (industry.value == value) return industry;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialises to the V2 API string.
|
||||||
|
String toJson() => value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/// Skill options for staff experience profiles.
|
||||||
|
///
|
||||||
|
/// Values match the V2 API format (UPPER_SNAKE_CASE).
|
||||||
|
enum StaffSkill {
|
||||||
|
/// Food service skill.
|
||||||
|
foodService('FOOD_SERVICE'),
|
||||||
|
|
||||||
|
/// Bartending skill.
|
||||||
|
bartending('BARTENDING'),
|
||||||
|
|
||||||
|
/// Event setup skill.
|
||||||
|
eventSetup('EVENT_SETUP'),
|
||||||
|
|
||||||
|
/// Hospitality skill.
|
||||||
|
hospitality('HOSPITALITY'),
|
||||||
|
|
||||||
|
/// Warehouse skill.
|
||||||
|
warehouse('WAREHOUSE'),
|
||||||
|
|
||||||
|
/// Customer service skill.
|
||||||
|
customerService('CUSTOMER_SERVICE'),
|
||||||
|
|
||||||
|
/// Cleaning skill.
|
||||||
|
cleaning('CLEANING'),
|
||||||
|
|
||||||
|
/// Security skill.
|
||||||
|
security('SECURITY'),
|
||||||
|
|
||||||
|
/// Retail skill.
|
||||||
|
retail('RETAIL'),
|
||||||
|
|
||||||
|
/// Driving skill.
|
||||||
|
driving('DRIVING'),
|
||||||
|
|
||||||
|
/// Cooking skill.
|
||||||
|
cooking('COOKING'),
|
||||||
|
|
||||||
|
/// Cashier skill.
|
||||||
|
cashier('CASHIER'),
|
||||||
|
|
||||||
|
/// Server skill.
|
||||||
|
server('SERVER'),
|
||||||
|
|
||||||
|
/// Barista skill.
|
||||||
|
barista('BARISTA'),
|
||||||
|
|
||||||
|
/// Host / hostess skill.
|
||||||
|
hostHostess('HOST_HOSTESS'),
|
||||||
|
|
||||||
|
/// Busser skill.
|
||||||
|
busser('BUSSER');
|
||||||
|
|
||||||
|
const StaffSkill(this.value);
|
||||||
|
|
||||||
|
/// The V2 API string representation.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
/// Deserialises from a V2 API string with safe fallback.
|
||||||
|
static StaffSkill? fromJson(String? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
for (final StaffSkill skill in StaffSkill.values) {
|
||||||
|
if (skill.value == value) return skill;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialises to the V2 API string.
|
||||||
|
String toJson() => value;
|
||||||
|
}
|
||||||
@@ -4,8 +4,6 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import 'package:staff_profile_experience/src/domain/repositories/experience_repository_interface.dart';
|
import 'package:staff_profile_experience/src/domain/repositories/experience_repository_interface.dart';
|
||||||
|
|
||||||
/// Implementation of [ExperienceRepositoryInterface] using the V2 API.
|
/// Implementation of [ExperienceRepositoryInterface] using the V2 API.
|
||||||
///
|
|
||||||
/// Replaces the previous Firebase Data Connect implementation.
|
|
||||||
class ExperienceRepositoryImpl implements ExperienceRepositoryInterface {
|
class ExperienceRepositoryImpl implements ExperienceRepositoryInterface {
|
||||||
/// Creates an [ExperienceRepositoryImpl].
|
/// Creates an [ExperienceRepositoryImpl].
|
||||||
ExperienceRepositoryImpl({required BaseApiService apiService})
|
ExperienceRepositoryImpl({required BaseApiService apiService})
|
||||||
@@ -14,30 +12,54 @@ class ExperienceRepositoryImpl implements ExperienceRepositoryInterface {
|
|||||||
final BaseApiService _api;
|
final BaseApiService _api;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getIndustries() async {
|
Future<List<StaffIndustry>> getIndustries() async {
|
||||||
final ApiResponse response =
|
final ApiResponse response = await _api.get(StaffEndpoints.industries);
|
||||||
await _api.get(StaffEndpoints.industries);
|
final List<dynamic> items =
|
||||||
final List<dynamic> items = response.data['industries'] as List<dynamic>? ?? <dynamic>[];
|
response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
return items.map((dynamic e) => e.toString()).toList();
|
return items
|
||||||
|
.map((dynamic e) => StaffIndustry.fromJson(e.toString()))
|
||||||
|
.whereType<StaffIndustry>()
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> getSkills() async {
|
Future<({List<StaffSkill> skills, List<String> customSkills})>
|
||||||
|
getSkills() async {
|
||||||
final ApiResponse response = await _api.get(StaffEndpoints.skills);
|
final ApiResponse response = await _api.get(StaffEndpoints.skills);
|
||||||
final List<dynamic> items = response.data['skills'] as List<dynamic>? ?? <dynamic>[];
|
final List<dynamic> items =
|
||||||
return items.map((dynamic e) => e.toString()).toList();
|
response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
|
|
||||||
|
final List<StaffSkill> skills = <StaffSkill>[];
|
||||||
|
final List<String> customSkills = <String>[];
|
||||||
|
|
||||||
|
for (final dynamic item in items) {
|
||||||
|
final String value = item.toString();
|
||||||
|
final StaffSkill? parsed = StaffSkill.fromJson(value);
|
||||||
|
if (parsed != null) {
|
||||||
|
skills.add(parsed);
|
||||||
|
} else {
|
||||||
|
customSkills.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (skills: skills, customSkills: customSkills);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> saveExperience(
|
Future<void> saveExperience(
|
||||||
List<String> industries,
|
List<StaffIndustry> industries,
|
||||||
List<String> skills,
|
List<StaffSkill> skills,
|
||||||
|
List<String> customSkills,
|
||||||
) async {
|
) async {
|
||||||
await _api.put(
|
await _api.put(
|
||||||
StaffEndpoints.personalInfo,
|
StaffEndpoints.experience,
|
||||||
data: <String, dynamic>{
|
data: <String, dynamic>{
|
||||||
'industries': industries,
|
'industries':
|
||||||
'skills': skills,
|
industries.map((StaffIndustry i) => i.value).toList(),
|
||||||
|
'skills': <String>[
|
||||||
|
...skills.map((StaffSkill s) => s.value),
|
||||||
|
...customSkills,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffIndustry, StaffSkill;
|
||||||
|
|
||||||
|
/// Arguments for the [SaveExperienceUseCase].
|
||||||
class SaveExperienceArguments extends UseCaseArgument {
|
class SaveExperienceArguments extends UseCaseArgument {
|
||||||
final List<String> industries;
|
/// Creates a [SaveExperienceArguments].
|
||||||
final List<String> skills;
|
|
||||||
|
|
||||||
const SaveExperienceArguments({
|
const SaveExperienceArguments({
|
||||||
required this.industries,
|
required this.industries,
|
||||||
required this.skills,
|
required this.skills,
|
||||||
|
this.customSkills = const <String>[],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Selected industries.
|
||||||
|
final List<StaffIndustry> industries;
|
||||||
|
|
||||||
|
/// Selected predefined skills.
|
||||||
|
final List<StaffSkill> skills;
|
||||||
|
|
||||||
|
/// Custom skills not in the [StaffSkill] enum.
|
||||||
|
final List<String> customSkills;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [industries, skills];
|
List<Object?> get props => <Object?>[industries, skills, customSkills];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffIndustry, StaffSkill;
|
||||||
|
|
||||||
/// Interface for accessing staff experience data.
|
/// Interface for accessing staff experience data.
|
||||||
abstract class ExperienceRepositoryInterface {
|
abstract class ExperienceRepositoryInterface {
|
||||||
/// Fetches the list of industries associated with the staff member.
|
/// Fetches the list of industries associated with the staff member.
|
||||||
Future<List<String>> getIndustries();
|
Future<List<StaffIndustry>> getIndustries();
|
||||||
|
|
||||||
/// Fetches the list of skills associated with the staff member.
|
/// Fetches the list of skills associated with the staff member.
|
||||||
Future<List<String>> getSkills();
|
///
|
||||||
|
/// Returns recognised [StaffSkill] values. Unrecognised API values are
|
||||||
|
/// returned in [customSkills].
|
||||||
|
Future<({List<StaffSkill> skills, List<String> customSkills})> getSkills();
|
||||||
|
|
||||||
/// Saves the staff member's experience (industries and skills).
|
/// Saves the staff member's experience (industries and skills).
|
||||||
Future<void> saveExperience(
|
Future<void> saveExperience(
|
||||||
List<String> industries,
|
List<StaffIndustry> industries,
|
||||||
List<String> skills,
|
List<StaffSkill> skills,
|
||||||
|
List<String> customSkills,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffIndustry;
|
||||||
|
|
||||||
import '../repositories/experience_repository_interface.dart';
|
import '../repositories/experience_repository_interface.dart';
|
||||||
|
|
||||||
/// Use case for fetching staff industries.
|
/// Use case for fetching staff industries.
|
||||||
class GetStaffIndustriesUseCase implements NoInputUseCase<List<String>> {
|
class GetStaffIndustriesUseCase
|
||||||
final ExperienceRepositoryInterface _repository;
|
implements NoInputUseCase<List<StaffIndustry>> {
|
||||||
|
/// Creates a [GetStaffIndustriesUseCase].
|
||||||
GetStaffIndustriesUseCase(this._repository);
|
GetStaffIndustriesUseCase(this._repository);
|
||||||
|
|
||||||
|
final ExperienceRepositoryInterface _repository;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> call() {
|
Future<List<StaffIndustry>> call() {
|
||||||
return _repository.getIndustries();
|
return _repository.getIndustries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffSkill;
|
||||||
|
|
||||||
import '../repositories/experience_repository_interface.dart';
|
import '../repositories/experience_repository_interface.dart';
|
||||||
|
|
||||||
/// Use case for fetching staff skills.
|
/// Use case for fetching staff skills.
|
||||||
class GetStaffSkillsUseCase implements NoInputUseCase<List<String>> {
|
class GetStaffSkillsUseCase
|
||||||
final ExperienceRepositoryInterface _repository;
|
implements
|
||||||
|
NoInputUseCase<({List<StaffSkill> skills, List<String> customSkills})> {
|
||||||
|
/// Creates a [GetStaffSkillsUseCase].
|
||||||
GetStaffSkillsUseCase(this._repository);
|
GetStaffSkillsUseCase(this._repository);
|
||||||
|
|
||||||
|
final ExperienceRepositoryInterface _repository;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<String>> call() {
|
Future<({List<StaffSkill> skills, List<String> customSkills})> call() {
|
||||||
return _repository.getSkills();
|
return _repository.getSkills();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
|
|
||||||
import '../arguments/save_experience_arguments.dart';
|
import '../arguments/save_experience_arguments.dart';
|
||||||
import '../repositories/experience_repository_interface.dart';
|
import '../repositories/experience_repository_interface.dart';
|
||||||
|
|
||||||
/// Use case for saving staff experience details.
|
/// Use case for saving staff experience details.
|
||||||
///
|
|
||||||
/// Delegates the saving logic to [ExperienceRepositoryInterface].
|
|
||||||
class SaveExperienceUseCase extends UseCase<SaveExperienceArguments, void> {
|
class SaveExperienceUseCase extends UseCase<SaveExperienceArguments, void> {
|
||||||
final ExperienceRepositoryInterface repository;
|
|
||||||
|
|
||||||
/// Creates a [SaveExperienceUseCase].
|
/// Creates a [SaveExperienceUseCase].
|
||||||
SaveExperienceUseCase(this.repository);
|
SaveExperienceUseCase(this.repository);
|
||||||
|
|
||||||
|
/// The experience repository.
|
||||||
|
final ExperienceRepositoryInterface repository;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> call(SaveExperienceArguments params) {
|
Future<void> call(SaveExperienceArguments params) {
|
||||||
return repository.saveExperience(
|
return repository.saveExperience(
|
||||||
params.industries,
|
params.industries,
|
||||||
params.skills,
|
params.skills,
|
||||||
|
params.customSkills,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,144 +1,26 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffIndustry, StaffSkill;
|
||||||
|
|
||||||
import '../../domain/arguments/save_experience_arguments.dart';
|
import '../../domain/arguments/save_experience_arguments.dart';
|
||||||
import '../../domain/usecases/get_staff_industries_usecase.dart';
|
import '../../domain/usecases/get_staff_industries_usecase.dart';
|
||||||
import '../../domain/usecases/get_staff_skills_usecase.dart';
|
import '../../domain/usecases/get_staff_skills_usecase.dart';
|
||||||
import '../../domain/usecases/save_experience_usecase.dart';
|
import '../../domain/usecases/save_experience_usecase.dart';
|
||||||
|
import 'experience_event.dart';
|
||||||
|
import 'experience_state.dart';
|
||||||
|
|
||||||
// Events
|
export 'experience_event.dart';
|
||||||
abstract class ExperienceEvent extends Equatable {
|
export 'experience_state.dart';
|
||||||
const ExperienceEvent();
|
|
||||||
|
|
||||||
@override
|
/// BLoC that manages the staff experience (industries & skills) selection.
|
||||||
List<Object?> get props => [];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExperienceLoaded extends ExperienceEvent {}
|
|
||||||
|
|
||||||
class ExperienceIndustryToggled extends ExperienceEvent {
|
|
||||||
final String industry;
|
|
||||||
const ExperienceIndustryToggled(this.industry);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [industry];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExperienceSkillToggled extends ExperienceEvent {
|
|
||||||
final String skill;
|
|
||||||
const ExperienceSkillToggled(this.skill);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [skill];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExperienceCustomSkillAdded extends ExperienceEvent {
|
|
||||||
final String skill;
|
|
||||||
const ExperienceCustomSkillAdded(this.skill);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [skill];
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExperienceSubmitted extends ExperienceEvent {}
|
|
||||||
|
|
||||||
// State
|
|
||||||
enum ExperienceStatus { initial, loading, success, failure }
|
|
||||||
|
|
||||||
class ExperienceState extends Equatable {
|
|
||||||
final ExperienceStatus status;
|
|
||||||
final List<String> selectedIndustries;
|
|
||||||
final List<String> selectedSkills;
|
|
||||||
final List<String> availableIndustries;
|
|
||||||
final List<String> availableSkills;
|
|
||||||
final String? errorMessage;
|
|
||||||
|
|
||||||
const ExperienceState({
|
|
||||||
this.status = ExperienceStatus.initial,
|
|
||||||
this.selectedIndustries = const [],
|
|
||||||
this.selectedSkills = const [],
|
|
||||||
this.availableIndustries = const [],
|
|
||||||
this.availableSkills = const [],
|
|
||||||
this.errorMessage,
|
|
||||||
});
|
|
||||||
|
|
||||||
ExperienceState copyWith({
|
|
||||||
ExperienceStatus? status,
|
|
||||||
List<String>? selectedIndustries,
|
|
||||||
List<String>? selectedSkills,
|
|
||||||
List<String>? availableIndustries,
|
|
||||||
List<String>? availableSkills,
|
|
||||||
String? errorMessage,
|
|
||||||
}) {
|
|
||||||
return ExperienceState(
|
|
||||||
status: status ?? this.status,
|
|
||||||
selectedIndustries: selectedIndustries ?? this.selectedIndustries,
|
|
||||||
selectedSkills: selectedSkills ?? this.selectedSkills,
|
|
||||||
availableIndustries: availableIndustries ?? this.availableIndustries,
|
|
||||||
availableSkills: availableSkills ?? this.availableSkills,
|
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
status,
|
|
||||||
selectedIndustries,
|
|
||||||
selectedSkills,
|
|
||||||
availableIndustries,
|
|
||||||
availableSkills,
|
|
||||||
errorMessage,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Available industry option values.
|
|
||||||
const List<String> _kAvailableIndustries = <String>[
|
|
||||||
'hospitality',
|
|
||||||
'food_service',
|
|
||||||
'warehouse',
|
|
||||||
'events',
|
|
||||||
'retail',
|
|
||||||
'healthcare',
|
|
||||||
'other',
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Available skill option values.
|
|
||||||
const List<String> _kAvailableSkills = <String>[
|
|
||||||
'food_service',
|
|
||||||
'bartending',
|
|
||||||
'event_setup',
|
|
||||||
'hospitality',
|
|
||||||
'warehouse',
|
|
||||||
'customer_service',
|
|
||||||
'cleaning',
|
|
||||||
'security',
|
|
||||||
'retail',
|
|
||||||
'driving',
|
|
||||||
'cooking',
|
|
||||||
'cashier',
|
|
||||||
'server',
|
|
||||||
'barista',
|
|
||||||
'host_hostess',
|
|
||||||
'busser',
|
|
||||||
];
|
|
||||||
|
|
||||||
// BLoC
|
|
||||||
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
||||||
with BlocErrorHandler<ExperienceState> {
|
with BlocErrorHandler<ExperienceState> {
|
||||||
final GetStaffIndustriesUseCase getIndustries;
|
/// Creates an [ExperienceBloc].
|
||||||
final GetStaffSkillsUseCase getSkills;
|
|
||||||
final SaveExperienceUseCase saveExperience;
|
|
||||||
|
|
||||||
ExperienceBloc({
|
ExperienceBloc({
|
||||||
required this.getIndustries,
|
required this.getIndustries,
|
||||||
required this.getSkills,
|
required this.getSkills,
|
||||||
required this.saveExperience,
|
required this.saveExperience,
|
||||||
}) : super(
|
}) : super(const ExperienceState()) {
|
||||||
const ExperienceState(
|
|
||||||
availableIndustries: _kAvailableIndustries,
|
|
||||||
availableSkills: _kAvailableSkills,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
on<ExperienceLoaded>(_onLoaded);
|
on<ExperienceLoaded>(_onLoaded);
|
||||||
on<ExperienceIndustryToggled>(_onIndustryToggled);
|
on<ExperienceIndustryToggled>(_onIndustryToggled);
|
||||||
on<ExperienceSkillToggled>(_onSkillToggled);
|
on<ExperienceSkillToggled>(_onSkillToggled);
|
||||||
@@ -148,6 +30,15 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
|||||||
add(ExperienceLoaded());
|
add(ExperienceLoaded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use case for fetching saved industries.
|
||||||
|
final GetStaffIndustriesUseCase getIndustries;
|
||||||
|
|
||||||
|
/// Use case for fetching saved skills.
|
||||||
|
final GetStaffSkillsUseCase getSkills;
|
||||||
|
|
||||||
|
/// Use case for saving experience selections.
|
||||||
|
final SaveExperienceUseCase saveExperience;
|
||||||
|
|
||||||
Future<void> _onLoaded(
|
Future<void> _onLoaded(
|
||||||
ExperienceLoaded event,
|
ExperienceLoaded event,
|
||||||
Emitter<ExperienceState> emit,
|
Emitter<ExperienceState> emit,
|
||||||
@@ -156,13 +47,16 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
|||||||
await handleError(
|
await handleError(
|
||||||
emit: emit.call,
|
emit: emit.call,
|
||||||
action: () async {
|
action: () async {
|
||||||
final results = await Future.wait([getIndustries(), getSkills()]);
|
final List<StaffIndustry> industries = await getIndustries();
|
||||||
|
final ({List<StaffSkill> skills, List<String> customSkills}) skillsResult =
|
||||||
|
await getSkills();
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
status: ExperienceStatus.initial,
|
status: ExperienceStatus.initial,
|
||||||
selectedIndustries: results[0],
|
selectedIndustries: industries,
|
||||||
selectedSkills: results[1],
|
selectedSkills: skillsResult.skills,
|
||||||
|
customSkills: skillsResult.customSkills,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -177,7 +71,8 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
|||||||
ExperienceIndustryToggled event,
|
ExperienceIndustryToggled event,
|
||||||
Emitter<ExperienceState> emit,
|
Emitter<ExperienceState> emit,
|
||||||
) {
|
) {
|
||||||
final industries = List<String>.from(state.selectedIndustries);
|
final List<StaffIndustry> industries =
|
||||||
|
List<StaffIndustry>.from(state.selectedIndustries);
|
||||||
if (industries.contains(event.industry)) {
|
if (industries.contains(event.industry)) {
|
||||||
industries.remove(event.industry);
|
industries.remove(event.industry);
|
||||||
} else {
|
} else {
|
||||||
@@ -190,7 +85,8 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
|||||||
ExperienceSkillToggled event,
|
ExperienceSkillToggled event,
|
||||||
Emitter<ExperienceState> emit,
|
Emitter<ExperienceState> emit,
|
||||||
) {
|
) {
|
||||||
final skills = List<String>.from(state.selectedSkills);
|
final List<StaffSkill> skills =
|
||||||
|
List<StaffSkill>.from(state.selectedSkills);
|
||||||
if (skills.contains(event.skill)) {
|
if (skills.contains(event.skill)) {
|
||||||
skills.remove(event.skill);
|
skills.remove(event.skill);
|
||||||
} else {
|
} else {
|
||||||
@@ -203,9 +99,10 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
|||||||
ExperienceCustomSkillAdded event,
|
ExperienceCustomSkillAdded event,
|
||||||
Emitter<ExperienceState> emit,
|
Emitter<ExperienceState> emit,
|
||||||
) {
|
) {
|
||||||
if (!state.selectedSkills.contains(event.skill)) {
|
if (!state.customSkills.contains(event.skill)) {
|
||||||
final skills = List<String>.from(state.selectedSkills)..add(event.skill);
|
final List<String> custom = List<String>.from(state.customSkills)
|
||||||
emit(state.copyWith(selectedSkills: skills));
|
..add(event.skill);
|
||||||
|
emit(state.copyWith(customSkills: custom));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +118,7 @@ class ExperienceBloc extends Bloc<ExperienceEvent, ExperienceState>
|
|||||||
SaveExperienceArguments(
|
SaveExperienceArguments(
|
||||||
industries: state.selectedIndustries,
|
industries: state.selectedIndustries,
|
||||||
skills: state.selectedSkills,
|
skills: state.selectedSkills,
|
||||||
|
customSkills: state.customSkills,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
emit(state.copyWith(status: ExperienceStatus.success));
|
emit(state.copyWith(status: ExperienceStatus.success));
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffIndustry, StaffSkill;
|
||||||
|
|
||||||
|
/// Base event for the experience BLoC.
|
||||||
|
abstract class ExperienceEvent extends Equatable {
|
||||||
|
/// Creates an [ExperienceEvent].
|
||||||
|
const ExperienceEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggers initial load of saved industries and skills.
|
||||||
|
class ExperienceLoaded extends ExperienceEvent {}
|
||||||
|
|
||||||
|
/// Toggles an industry selection on or off.
|
||||||
|
class ExperienceIndustryToggled extends ExperienceEvent {
|
||||||
|
/// Creates an [ExperienceIndustryToggled] event.
|
||||||
|
const ExperienceIndustryToggled(this.industry);
|
||||||
|
|
||||||
|
/// The industry to toggle.
|
||||||
|
final StaffIndustry industry;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[industry];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles a skill selection on or off.
|
||||||
|
class ExperienceSkillToggled extends ExperienceEvent {
|
||||||
|
/// Creates an [ExperienceSkillToggled] event.
|
||||||
|
const ExperienceSkillToggled(this.skill);
|
||||||
|
|
||||||
|
/// The skill to toggle.
|
||||||
|
final StaffSkill skill;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[skill];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a custom skill not in the predefined [StaffSkill] enum.
|
||||||
|
class ExperienceCustomSkillAdded extends ExperienceEvent {
|
||||||
|
/// Creates an [ExperienceCustomSkillAdded] event.
|
||||||
|
const ExperienceCustomSkillAdded(this.skill);
|
||||||
|
|
||||||
|
/// The custom skill value to add.
|
||||||
|
final String skill;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[skill];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Submits the selected industries and skills to the backend.
|
||||||
|
class ExperienceSubmitted extends ExperienceEvent {}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffIndustry, StaffSkill;
|
||||||
|
|
||||||
|
/// Status of the experience feature.
|
||||||
|
enum ExperienceStatus {
|
||||||
|
/// Initial state before any action.
|
||||||
|
initial,
|
||||||
|
|
||||||
|
/// Loading data from the backend.
|
||||||
|
loading,
|
||||||
|
|
||||||
|
/// Operation completed successfully.
|
||||||
|
success,
|
||||||
|
|
||||||
|
/// An error occurred.
|
||||||
|
failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State for the experience BLoC.
|
||||||
|
class ExperienceState extends Equatable {
|
||||||
|
/// Creates an [ExperienceState].
|
||||||
|
const ExperienceState({
|
||||||
|
this.status = ExperienceStatus.initial,
|
||||||
|
this.selectedIndustries = const <StaffIndustry>[],
|
||||||
|
this.selectedSkills = const <StaffSkill>[],
|
||||||
|
this.customSkills = const <String>[],
|
||||||
|
this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Current operation status.
|
||||||
|
final ExperienceStatus status;
|
||||||
|
|
||||||
|
/// Industries the staff member has selected.
|
||||||
|
final List<StaffIndustry> selectedIndustries;
|
||||||
|
|
||||||
|
/// Skills the staff member has selected.
|
||||||
|
final List<StaffSkill> selectedSkills;
|
||||||
|
|
||||||
|
/// Custom skills not in [StaffSkill] that the user added.
|
||||||
|
final List<String> customSkills;
|
||||||
|
|
||||||
|
/// Error message key when [status] is [ExperienceStatus.failure].
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
/// All selected skill values as API strings (enum + custom combined).
|
||||||
|
List<String> get allSkillValues =>
|
||||||
|
<String>[
|
||||||
|
...selectedSkills.map((StaffSkill s) => s.value),
|
||||||
|
...customSkills,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Creates a copy with the given fields replaced.
|
||||||
|
ExperienceState copyWith({
|
||||||
|
ExperienceStatus? status,
|
||||||
|
List<StaffIndustry>? selectedIndustries,
|
||||||
|
List<StaffSkill>? selectedSkills,
|
||||||
|
List<String>? customSkills,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return ExperienceState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
selectedIndustries: selectedIndustries ?? this.selectedIndustries,
|
||||||
|
selectedSkills: selectedSkills ?? this.selectedSkills,
|
||||||
|
customSkills: customSkills ?? this.customSkills,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => <Object?>[
|
||||||
|
status,
|
||||||
|
selectedIndustries,
|
||||||
|
selectedSkills,
|
||||||
|
customSkills,
|
||||||
|
errorMessage,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -4,90 +4,33 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart' show StaffIndustry, StaffSkill;
|
||||||
|
|
||||||
import '../blocs/experience_bloc.dart';
|
import '../blocs/experience_bloc.dart';
|
||||||
import '../widgets/experience_section_title.dart';
|
import '../widgets/experience_section_title.dart';
|
||||||
|
|
||||||
|
/// Page for selecting staff industries and skills.
|
||||||
class ExperiencePage extends StatelessWidget {
|
class ExperiencePage extends StatelessWidget {
|
||||||
|
/// Creates an [ExperiencePage].
|
||||||
const ExperiencePage({super.key});
|
const ExperiencePage({super.key});
|
||||||
|
|
||||||
String _getIndustryLabel(dynamic node, String industry) {
|
|
||||||
switch (industry) {
|
|
||||||
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 industry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getSkillLabel(dynamic node, String skill) {
|
|
||||||
switch (skill) {
|
|
||||||
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 'driving':
|
|
||||||
return node.driving;
|
|
||||||
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 skill;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final i18n = Translations.of(context).staff.onboarding.experience;
|
final dynamic i18n = Translations.of(context).staff.onboarding.experience;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: UiAppBar(
|
appBar: UiAppBar(
|
||||||
title: i18n.title,
|
title: i18n.title as String,
|
||||||
onLeadingPressed: () => Modular.to.toProfile(),
|
onLeadingPressed: () => Modular.to.toProfile(),
|
||||||
),
|
),
|
||||||
body: BlocProvider<ExperienceBloc>(
|
body: BlocProvider<ExperienceBloc>(
|
||||||
create: (context) => Modular.get<ExperienceBloc>(),
|
create: (BuildContext context) => Modular.get<ExperienceBloc>(),
|
||||||
child: BlocConsumer<ExperienceBloc, ExperienceState>(
|
child: BlocConsumer<ExperienceBloc, ExperienceState>(
|
||||||
listener: (context, state) {
|
listener: (BuildContext context, ExperienceState state) {
|
||||||
if (state.status == ExperienceStatus.success) {
|
if (state.status == ExperienceStatus.success) {
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
context,
|
context,
|
||||||
message: 'Experience saved successfully',
|
message: i18n.save_success as String,
|
||||||
type: UiSnackbarType.success,
|
type: UiSnackbarType.success,
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
bottom: 120,
|
bottom: 120,
|
||||||
@@ -100,7 +43,7 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
message: state.errorMessage != null
|
message: state.errorMessage != null
|
||||||
? translateErrorKey(state.errorMessage!)
|
? translateErrorKey(state.errorMessage!)
|
||||||
: 'An error occurred',
|
: i18n.save_error as String,
|
||||||
type: UiSnackbarType.error,
|
type: UiSnackbarType.error,
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
bottom: 120,
|
bottom: 120,
|
||||||
@@ -110,65 +53,28 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (BuildContext context, ExperienceState state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(UiConstants.space5),
|
padding: const EdgeInsets.all(UiConstants.space5),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: <Widget>[
|
||||||
ExperienceSectionTitle(
|
ExperienceSectionTitle(
|
||||||
title: i18n.industries_title,
|
title: i18n.industries_title as String,
|
||||||
subtitle: i18n.industries_subtitle,
|
subtitle: i18n.industries_subtitle as String,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space3),
|
const SizedBox(height: UiConstants.space3),
|
||||||
Wrap(
|
_buildIndustryChips(context, state, i18n),
|
||||||
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(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space10),
|
const SizedBox(height: UiConstants.space10),
|
||||||
ExperienceSectionTitle(
|
ExperienceSectionTitle(
|
||||||
title: i18n.skills_title,
|
title: i18n.skills_title as String,
|
||||||
subtitle: i18n.skills_subtitle,
|
subtitle: i18n.skills_subtitle as String,
|
||||||
),
|
),
|
||||||
const SizedBox(height: UiConstants.space3),
|
const SizedBox(height: UiConstants.space3),
|
||||||
Wrap(
|
_buildSkillChips(context, state, i18n),
|
||||||
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(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -182,28 +88,45 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCustomSkillsList(ExperienceState state, dynamic i18n) {
|
Widget _buildIndustryChips(
|
||||||
final customSkills = state.selectedSkills
|
BuildContext context,
|
||||||
.where((s) => !state.availableSkills.contains(s))
|
ExperienceState state,
|
||||||
.toList();
|
dynamic i18n,
|
||||||
if (customSkills.isEmpty) return const SizedBox.shrink();
|
) {
|
||||||
|
return Wrap(
|
||||||
|
spacing: UiConstants.space2,
|
||||||
|
runSpacing: UiConstants.space2,
|
||||||
|
children: StaffIndustry.values.map((StaffIndustry industry) {
|
||||||
|
final bool isSelected = state.selectedIndustries.contains(industry);
|
||||||
|
return UiChip(
|
||||||
|
label: _getIndustryLabel(i18n.industries, industry),
|
||||||
|
isSelected: isSelected,
|
||||||
|
onTap: () => BlocProvider.of<ExperienceBloc>(context)
|
||||||
|
.add(ExperienceIndustryToggled(industry)),
|
||||||
|
variant: isSelected ? UiChipVariant.primary : UiChipVariant.secondary,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
Widget _buildSkillChips(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
BuildContext context,
|
||||||
children: [
|
ExperienceState state,
|
||||||
Text(
|
dynamic i18n,
|
||||||
i18n.custom_skills_title,
|
) {
|
||||||
style: UiTypography.body2m.textSecondary,
|
return Wrap(
|
||||||
),
|
spacing: UiConstants.space2,
|
||||||
const SizedBox(height: UiConstants.space2),
|
runSpacing: UiConstants.space2,
|
||||||
Wrap(
|
children: StaffSkill.values.map((StaffSkill skill) {
|
||||||
spacing: UiConstants.space2,
|
final bool isSelected = state.selectedSkills.contains(skill);
|
||||||
runSpacing: UiConstants.space2,
|
return UiChip(
|
||||||
children: customSkills.map((skill) {
|
label: _getSkillLabel(i18n.skills, skill),
|
||||||
return UiChip(label: skill, variant: UiChipVariant.accent);
|
isSelected: isSelected,
|
||||||
}).toList(),
|
onTap: () => BlocProvider.of<ExperienceBloc>(context)
|
||||||
),
|
.add(ExperienceSkillToggled(skill)),
|
||||||
],
|
variant: isSelected ? UiChipVariant.primary : UiChipVariant.secondary,
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +135,7 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
ExperienceState state,
|
ExperienceState state,
|
||||||
dynamic i18n,
|
dynamic i18n,
|
||||||
) {
|
) {
|
||||||
|
final bool isLoading = state.status == ExperienceStatus.loading;
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(UiConstants.space4),
|
padding: const EdgeInsets.all(UiConstants.space4),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
@@ -220,24 +144,20 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: UiButton.primary(
|
child: UiButton.primary(
|
||||||
onPressed: state.status == ExperienceStatus.loading
|
onPressed: isLoading
|
||||||
? null
|
? null
|
||||||
: () => BlocProvider.of<ExperienceBloc>(
|
: () => BlocProvider.of<ExperienceBloc>(context)
|
||||||
context,
|
.add(ExperienceSubmitted()),
|
||||||
).add(ExperienceSubmitted()),
|
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
text: state.status == ExperienceStatus.loading
|
text: isLoading ? null : i18n.save_button as String,
|
||||||
? null
|
child: isLoading
|
||||||
: i18n.save_button,
|
|
||||||
child: state.status == ExperienceStatus.loading
|
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
height: UiConstants.iconMd,
|
height: UiConstants.iconMd,
|
||||||
width: UiConstants.iconMd,
|
width: UiConstants.iconMd,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
valueColor:
|
||||||
UiColors.white,
|
AlwaysStoppedAnimation<Color>(UiColors.white),
|
||||||
), // UiColors.primaryForeground is white mostly
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
@@ -245,4 +165,66 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps a [StaffIndustry] to its localized label.
|
||||||
|
String _getIndustryLabel(dynamic node, StaffIndustry industry) {
|
||||||
|
switch (industry) {
|
||||||
|
case StaffIndustry.hospitality:
|
||||||
|
return node.hospitality as String;
|
||||||
|
case StaffIndustry.foodService:
|
||||||
|
return node.food_service as String;
|
||||||
|
case StaffIndustry.warehouse:
|
||||||
|
return node.warehouse as String;
|
||||||
|
case StaffIndustry.events:
|
||||||
|
return node.events as String;
|
||||||
|
case StaffIndustry.retail:
|
||||||
|
return node.retail as String;
|
||||||
|
case StaffIndustry.healthcare:
|
||||||
|
return node.healthcare as String;
|
||||||
|
case StaffIndustry.catering:
|
||||||
|
return node.catering as String;
|
||||||
|
case StaffIndustry.cafe:
|
||||||
|
return node.cafe as String;
|
||||||
|
case StaffIndustry.other:
|
||||||
|
return node.other as String;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps a [StaffSkill] to its localized label.
|
||||||
|
String _getSkillLabel(dynamic node, StaffSkill skill) {
|
||||||
|
switch (skill) {
|
||||||
|
case StaffSkill.foodService:
|
||||||
|
return node.food_service as String;
|
||||||
|
case StaffSkill.bartending:
|
||||||
|
return node.bartending as String;
|
||||||
|
case StaffSkill.eventSetup:
|
||||||
|
return node.event_setup as String;
|
||||||
|
case StaffSkill.hospitality:
|
||||||
|
return node.hospitality as String;
|
||||||
|
case StaffSkill.warehouse:
|
||||||
|
return node.warehouse as String;
|
||||||
|
case StaffSkill.customerService:
|
||||||
|
return node.customer_service as String;
|
||||||
|
case StaffSkill.cleaning:
|
||||||
|
return node.cleaning as String;
|
||||||
|
case StaffSkill.security:
|
||||||
|
return node.security as String;
|
||||||
|
case StaffSkill.retail:
|
||||||
|
return node.retail as String;
|
||||||
|
case StaffSkill.driving:
|
||||||
|
return node.driving as String;
|
||||||
|
case StaffSkill.cooking:
|
||||||
|
return node.cooking as String;
|
||||||
|
case StaffSkill.cashier:
|
||||||
|
return node.cashier as String;
|
||||||
|
case StaffSkill.server:
|
||||||
|
return node.server as String;
|
||||||
|
case StaffSkill.barista:
|
||||||
|
return node.barista as String;
|
||||||
|
case StaffSkill.hostHostess:
|
||||||
|
return node.host_hostess as String;
|
||||||
|
case StaffSkill.busser:
|
||||||
|
return node.busser as String;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user