You've already forked RekomenciMobile
feat: add reasume validation, and update study grade enum
This commit is contained in:
@@ -125,31 +125,39 @@ data class EducationDTO(
|
||||
|
||||
@Serializable
|
||||
enum class EducationGradesDTO {
|
||||
@SerialName("common")
|
||||
Common,
|
||||
@SerialName("basic_general_education")
|
||||
BasicGeneralEducation,
|
||||
|
||||
@SerialName("middle")
|
||||
Middle,
|
||||
@SerialName("secondary_general_education")
|
||||
SecondaryGeneralEducation,
|
||||
|
||||
@SerialName("middle_spec")
|
||||
MiddleSpec,
|
||||
@SerialName("secondary_professional_education")
|
||||
SecondaryProfessionalEducation,
|
||||
|
||||
@SerialName("high_not_finished")
|
||||
HighNotFinished,
|
||||
@SerialName("bachelor")
|
||||
Bachelor,
|
||||
|
||||
@SerialName("high")
|
||||
High,
|
||||
@SerialName("specialist")
|
||||
Specialist,
|
||||
|
||||
@SerialName("additional")
|
||||
Additional;
|
||||
@SerialName("master")
|
||||
Master,
|
||||
|
||||
@SerialName("postgraduate_studies")
|
||||
PostgraduateStudies,
|
||||
|
||||
@SerialName("other")
|
||||
Other;
|
||||
|
||||
fun mapToDomain(): EducationGrades = when (this) {
|
||||
Common -> EducationGrades.Common
|
||||
Middle -> EducationGrades.Middle
|
||||
MiddleSpec -> EducationGrades.MiddleSpec
|
||||
HighNotFinished -> EducationGrades.HighNotFinished
|
||||
High -> EducationGrades.High
|
||||
Additional -> EducationGrades.Additional
|
||||
BasicGeneralEducation -> EducationGrades.BasicGeneralEducation
|
||||
SecondaryGeneralEducation -> EducationGrades.SecondaryGeneralEducation
|
||||
SecondaryProfessionalEducation -> EducationGrades.SecondaryProfessionalEducation
|
||||
Bachelor -> EducationGrades.Bachelor
|
||||
Specialist -> EducationGrades.Specialist
|
||||
Master -> EducationGrades.Master
|
||||
PostgraduateStudies -> EducationGrades.PostgraduateStudies
|
||||
Other -> EducationGrades.Other
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,12 +39,14 @@ data class Education(
|
||||
)
|
||||
|
||||
enum class EducationGrades {
|
||||
Common,
|
||||
Middle,
|
||||
MiddleSpec,
|
||||
HighNotFinished,
|
||||
High,
|
||||
Additional
|
||||
BasicGeneralEducation,
|
||||
SecondaryGeneralEducation,
|
||||
SecondaryProfessionalEducation,
|
||||
Bachelor,
|
||||
Specialist,
|
||||
Master,
|
||||
PostgraduateStudies,
|
||||
Other
|
||||
}
|
||||
|
||||
data class Project(
|
||||
|
||||
+50
-1
@@ -1,10 +1,14 @@
|
||||
package com.prodhack.moscow2025.domain.usecase.auth
|
||||
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import com.prodhack.moscow2025.domain.models.AuthField
|
||||
import com.prodhack.moscow2025.domain.models.ExperienceType
|
||||
import com.prodhack.moscow2025.domain.models.PhoneNumberPattern
|
||||
import com.prodhack.moscow2025.domain.models.Project
|
||||
import com.prodhack.moscow2025.domain.models.ResumeField
|
||||
import com.prodhack.moscow2025.domain.models.WorkExperience
|
||||
import com.prodhack.moscow2025.presentation.screens.createResume.UIEducation
|
||||
import org.koin.core.annotation.Single
|
||||
|
||||
data class ValidationResult<T>(
|
||||
@@ -87,7 +91,11 @@ class ValidateFieldsUseCase {
|
||||
about: String,
|
||||
position: String,
|
||||
experience: ExperienceType?,
|
||||
keySkills: List<String>
|
||||
keySkills: List<String>,
|
||||
city: String,
|
||||
workExperience: List<WorkExperience>,
|
||||
education: List<UIEducation>,
|
||||
projects: List<Project>
|
||||
): ValidationResult<ResumeField> {
|
||||
val errors = buildMap {
|
||||
if (about.isBlank()) put(ResumeField.About, "Без этого мы не сможем рассчитать вашу ЗП")
|
||||
@@ -101,6 +109,47 @@ class ValidateFieldsUseCase {
|
||||
)
|
||||
if (keySkills.isEmpty()) put(ResumeField.KeySkills, "Укажите хотя бы один навык")
|
||||
|
||||
if (city.isEmpty()) put(ResumeField.City, "Без этого мы не сможем рассчитать вашу ЗП")
|
||||
workExperience.forEachIndexed { index, exp ->
|
||||
if (exp.place.isBlank()) put(
|
||||
ResumeField.WorkExperiencePlace(index),
|
||||
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||
)
|
||||
if (exp.description.isBlank()) put(
|
||||
ResumeField.WorkExperienceDescription(index),
|
||||
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||
)
|
||||
if (exp.monthDuration == null) put(
|
||||
ResumeField.WorkExperienceMonthDuration(index),
|
||||
"Введите корректное число"
|
||||
)
|
||||
}
|
||||
|
||||
education.forEachIndexed { index, educ ->
|
||||
if (educ.place.isBlank()) put(
|
||||
ResumeField.EducationPlace(index),
|
||||
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||
)
|
||||
if (educ.description.isBlank()) put(
|
||||
ResumeField.EducationDescription(index),
|
||||
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||
)
|
||||
if (educ.specialization.isBlank()) put(
|
||||
ResumeField.EducationSpecialization(index),
|
||||
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||
)
|
||||
}
|
||||
|
||||
projects.forEachIndexed { index, prj ->
|
||||
if (prj.name.isBlank()) put(
|
||||
ResumeField.ProjectName(index),
|
||||
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||
)
|
||||
if (prj.description.isBlank()) put(
|
||||
ResumeField.ProjectDescription(index),
|
||||
"Без этого мы не сможем рассчитать вашу ЗП"
|
||||
)
|
||||
}
|
||||
}
|
||||
return ValidationResult(errors)
|
||||
}
|
||||
|
||||
+31
-12
@@ -85,12 +85,16 @@ fun CreateResumeScreen(
|
||||
label = "Какая должность вас интересует?",
|
||||
error = formState.value.errors[ResumeField.Position]
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
|
||||
TTTextField(
|
||||
value = formState.value.city,
|
||||
onValueChange = viewModel::onCityChange,
|
||||
label = "Ваш город",
|
||||
error = formState.value.errors[ResumeField.City]
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
|
||||
TTTextField(
|
||||
value = formState.value.about,
|
||||
onValueChange = viewModel::onAboutChange,
|
||||
@@ -99,6 +103,8 @@ fun CreateResumeScreen(
|
||||
label = "Расскажите о себе",
|
||||
error = formState.value.errors[ResumeField.Position]
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
|
||||
TTTextFieldWithDropdown(
|
||||
value = formState.value.experience?.friendlyName ?: "",
|
||||
onValueChange = {},
|
||||
@@ -112,6 +118,8 @@ fun CreateResumeScreen(
|
||||
},
|
||||
onDropdownItemSelected = viewModel::onExperienceSelect
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
|
||||
TTTextFieldWithSearch(
|
||||
value = viewModel.skillSearchQuery.value,
|
||||
onValueChange = {
|
||||
@@ -121,7 +129,7 @@ fun CreateResumeScreen(
|
||||
error = formState.value.errors[ResumeField.Experience],
|
||||
dropdownItems = viewModel.suggestedSkills.collectAsState(emptyList()).value,
|
||||
dropDownItem = {
|
||||
Text(text = it, style = typography.titleMedium, fontSize = 16.sp)
|
||||
Text(text = it, style = typography.labelLarge, fontSize = 16.sp)
|
||||
},
|
||||
onDropdownItemSelected = viewModel::onAddSkill,
|
||||
trailingIcon = {
|
||||
@@ -151,6 +159,8 @@ fun CreateResumeScreen(
|
||||
}
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
@@ -167,7 +177,7 @@ fun CreateResumeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.large * 2))
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -195,7 +205,7 @@ fun CreateResumeScreen(
|
||||
label = "Место работы",
|
||||
error = formState.value.errors[ResumeField.WorkExperiencePlace(index)]
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = workExp.description,
|
||||
onValueChange = {
|
||||
@@ -206,7 +216,7 @@ fun CreateResumeScreen(
|
||||
label = "Расскажите подробнее",
|
||||
error = formState.value.errors[ResumeField.WorkExperienceDescription(index)]
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = workExp.monthDuration?.toString() ?: "",
|
||||
onValueChange = {
|
||||
@@ -215,6 +225,7 @@ fun CreateResumeScreen(
|
||||
label = "Продолжительность (в месяцах)",
|
||||
error = formState.value.errors[ResumeField.WorkExperienceMonthDuration(index)]
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
}
|
||||
|
||||
if (formState.value.workExperience.isEmpty()) {
|
||||
@@ -250,7 +261,7 @@ fun CreateResumeScreen(
|
||||
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Образование:",
|
||||
text = "Ваше образование:",
|
||||
style = typography.titleMedium,
|
||||
fontSize = 20.sp,
|
||||
textAlign = TextAlign.Center
|
||||
@@ -272,7 +283,7 @@ fun CreateResumeScreen(
|
||||
label = "Учебное заведение",
|
||||
error = formState.value.errors[ResumeField.EducationPlace(index)]
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
TTTextFieldWithDropdown(
|
||||
value = education.grade.friendlyName,
|
||||
onValueChange = {},
|
||||
@@ -282,26 +293,31 @@ fun CreateResumeScreen(
|
||||
error = formState.value.errors[ResumeField.EducationGrade(index)],
|
||||
dropdownItems = viewModel.educationGradeOptions,
|
||||
dropDownItem = {
|
||||
Text(text = it.friendlyName, style = typography.titleMedium, fontSize = 16.sp)
|
||||
Text(
|
||||
text = it.friendlyName,
|
||||
style = typography.labelLarge,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
},
|
||||
onDropdownItemSelected = { viewModel.changeEducationGrade(index, it) }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = education.specialization,
|
||||
onValueChange = { viewModel.changeEducationSpecialization(index, it) },
|
||||
label = "Специализация",
|
||||
error = formState.value.errors[ResumeField.EducationSpecialization(index)]
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = education.description,
|
||||
onValueChange = { viewModel.changeEducationDescription(index, it) },
|
||||
singleLine = false,
|
||||
maxLines = 10,
|
||||
label = "Расскажите подробнее",
|
||||
label = "Расскажите подробнее (опционально)",
|
||||
error = formState.value.errors[ResumeField.EducationDescription(index)]
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
}
|
||||
|
||||
if (formState.value.education.isEmpty()) {
|
||||
@@ -338,7 +354,7 @@ fun CreateResumeScreen(
|
||||
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Проекты:",
|
||||
text = "Интересные проекты:",
|
||||
style = typography.titleMedium,
|
||||
fontSize = 20.sp,
|
||||
textAlign = TextAlign.Center
|
||||
@@ -360,7 +376,7 @@ fun CreateResumeScreen(
|
||||
label = "Название проекта",
|
||||
error = formState.value.errors[ResumeField.ProjectName(index)]
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
TTTextField(
|
||||
value = project.description,
|
||||
onValueChange = { viewModel.changeProjectDescription(index, it) },
|
||||
@@ -369,6 +385,7 @@ fun CreateResumeScreen(
|
||||
label = "Расскажите подробнее",
|
||||
error = formState.value.errors[ResumeField.ProjectDescription(index)]
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.medium))
|
||||
}
|
||||
|
||||
if (formState.value.projects.isEmpty()) {
|
||||
@@ -407,6 +424,8 @@ fun CreateResumeScreen(
|
||||
buttonText = "Узнать свою ЗП",
|
||||
isLoading = viewModel.resumeFillState.collectAsState().value.isLoading
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Paddings.large))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-20
@@ -59,21 +59,40 @@ data class UIEducation(
|
||||
val description: String
|
||||
)
|
||||
|
||||
//основное общее образование — basic_general_education
|
||||
//
|
||||
//среднее общее образование — secondary_general_education
|
||||
//
|
||||
//среднее профессиональное образование — secondary_professional_education
|
||||
//
|
||||
//бакалавриат — bachelor
|
||||
//
|
||||
//специалитет — specialist
|
||||
//
|
||||
//магистратура — master
|
||||
//
|
||||
//подготовка кадров высшей квалификации (аспірантура, ординатура, докторантура) — postgraduate_studies
|
||||
sealed class UIEducationGrade(val friendlyName: String) {
|
||||
data object Common : UIEducationGrade("Общее")
|
||||
data object Middle : UIEducationGrade("Среднее")
|
||||
data object MiddleSpec : UIEducationGrade("Средне-специальное")
|
||||
data object HighNotFinished : UIEducationGrade("Неоконченное высшее")
|
||||
data object High : UIEducationGrade("Высшее")
|
||||
data object Additional : UIEducationGrade("Другое")
|
||||
data object BasicGeneralEducation : UIEducationGrade("Общее")
|
||||
data object SecondaryGeneralEducation : UIEducationGrade("Среднее")
|
||||
data object SecondaryProfessionalEducation : UIEducationGrade("Средне-специальное")
|
||||
data object Bachelor : UIEducationGrade("Бакалавриат")
|
||||
data object Specialist : UIEducationGrade("Специалитет")
|
||||
data object Master : UIEducationGrade("Магистратура")
|
||||
|
||||
data object PostgraduateStudies: UIEducationGrade("Аспирантура и выше")
|
||||
|
||||
data object Other: UIEducationGrade("Другое")
|
||||
|
||||
fun mapToDomain(): EducationGrades = when (this) {
|
||||
Common -> EducationGrades.Common
|
||||
Middle -> EducationGrades.Middle
|
||||
MiddleSpec -> EducationGrades.MiddleSpec
|
||||
HighNotFinished -> EducationGrades.HighNotFinished
|
||||
High -> EducationGrades.High
|
||||
Additional -> EducationGrades.Additional
|
||||
BasicGeneralEducation -> EducationGrades.BasicGeneralEducation
|
||||
SecondaryGeneralEducation -> EducationGrades.SecondaryGeneralEducation
|
||||
SecondaryProfessionalEducation -> EducationGrades.SecondaryProfessionalEducation
|
||||
Bachelor -> EducationGrades.Bachelor
|
||||
Specialist -> EducationGrades.Specialist
|
||||
Master -> EducationGrades.Master
|
||||
PostgraduateStudies -> EducationGrades.PostgraduateStudies
|
||||
Other -> EducationGrades.Other
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +223,8 @@ class CreateResumeViewModel(
|
||||
experience.copy(
|
||||
monthDuration = it
|
||||
)
|
||||
} ?: experience
|
||||
}
|
||||
?: if (value.isEmpty()) experience.copy(monthDuration = null) else experience
|
||||
} else experience
|
||||
},
|
||||
errors = it.errors
|
||||
@@ -229,12 +249,12 @@ class CreateResumeViewModel(
|
||||
|
||||
// Education
|
||||
val educationGradeOptions = listOf(
|
||||
UIEducationGrade.Common,
|
||||
UIEducationGrade.Middle,
|
||||
UIEducationGrade.MiddleSpec,
|
||||
UIEducationGrade.HighNotFinished,
|
||||
UIEducationGrade.High,
|
||||
UIEducationGrade.Additional
|
||||
UIEducationGrade.BasicGeneralEducation,
|
||||
UIEducationGrade.SecondaryGeneralEducation,
|
||||
UIEducationGrade.SecondaryProfessionalEducation,
|
||||
UIEducationGrade.Bachelor,
|
||||
UIEducationGrade.Specialist,
|
||||
UIEducationGrade.Master
|
||||
)
|
||||
|
||||
fun addNewEducation() {
|
||||
@@ -242,7 +262,7 @@ class CreateResumeViewModel(
|
||||
it.copy(
|
||||
education = it.education + UIEducation(
|
||||
place = "",
|
||||
grade = UIEducationGrade.High,
|
||||
grade = UIEducationGrade.Specialist,
|
||||
specialization = "",
|
||||
description = ""
|
||||
)
|
||||
@@ -332,6 +352,10 @@ class CreateResumeViewModel(
|
||||
position = _formStateFillResume.value.position,
|
||||
experience = _formStateFillResume.value.experience?.mapToDomain(),
|
||||
keySkills = _formStateFillResume.value.keySkills.toList(),
|
||||
city = _formStateFillResume.value.city,
|
||||
workExperience = _formStateFillResume.value.workExperience,
|
||||
education = _formStateFillResume.value.education,
|
||||
projects = _formStateFillResume.value.projects
|
||||
)
|
||||
|
||||
if (!validation.isValid) {
|
||||
|
||||
Reference in New Issue
Block a user