Add residence picker to contractor create/edit screens

Kotlin/KMM:
- Update Contractor model with optional residenceId and specialties array
- Rename averageRating to rating, update address field names
- Add ContractorMinimal model for task references
- Add residence picker and multi-select specialty chips to AddContractorDialog
- Fix ContractorsScreen and ContractorDetailScreen field references

iOS:
- Rewrite ContractorFormSheet with residence and specialty pickers
- Update ContractorDetailView with FlowLayout for specialties
- Add FlowLayout component for wrapping badge layouts
- Fix ContractorCard and CompleteTaskView field references
- Update ContractorFormState with residence/specialty selection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-29 18:42:18 -06:00
parent c748f792d0
commit b0838d85df
22 changed files with 1472 additions and 1200 deletions

View File

@@ -10,17 +10,21 @@ struct ContractorFormState: FormState {
var company = FormField<String>()
var phone = FormField<String>()
var email = FormField<String>()
var secondaryPhone = FormField<String>()
var specialty = FormField<String>()
var licenseNumber = FormField<String>()
var website = FormField<String>()
var address = FormField<String>()
var streetAddress = FormField<String>()
var city = FormField<String>()
var state = FormField<String>()
var zipCode = FormField<String>()
var stateProvince = FormField<String>()
var postalCode = FormField<String>()
var notes = FormField<String>()
var isFavorite: Bool = false
// Residence selection (optional - nil means personal contractor)
var selectedResidenceId: Int32?
var selectedResidenceName: String?
// Specialty IDs (multiple selection)
var selectedSpecialtyIds: [Int32] = []
// For edit mode
var existingContractorId: Int32?
@@ -48,16 +52,16 @@ struct ContractorFormState: FormState {
company = FormField<String>()
phone = FormField<String>()
email = FormField<String>()
secondaryPhone = FormField<String>()
specialty = FormField<String>()
licenseNumber = FormField<String>()
website = FormField<String>()
address = FormField<String>()
streetAddress = FormField<String>()
city = FormField<String>()
state = FormField<String>()
zipCode = FormField<String>()
stateProvince = FormField<String>()
postalCode = FormField<String>()
notes = FormField<String>()
isFavorite = false
selectedResidenceId = nil
selectedResidenceName = nil
selectedSpecialtyIds = []
existingContractorId = nil
}
@@ -65,20 +69,19 @@ struct ContractorFormState: FormState {
func toCreateRequest() -> ContractorCreateRequest {
ContractorCreateRequest(
name: name.trimmedValue,
residenceId: selectedResidenceId.map { KotlinInt(int: $0) },
company: company.isEmpty ? nil : company.trimmedValue,
phone: phone.isEmpty ? nil : phone.trimmedValue,
email: email.isEmpty ? nil : email.trimmedValue,
secondaryPhone: secondaryPhone.isEmpty ? nil : secondaryPhone.trimmedValue,
specialty: specialty.isEmpty ? nil : specialty.trimmedValue,
licenseNumber: licenseNumber.isEmpty ? nil : licenseNumber.trimmedValue,
website: website.isEmpty ? nil : website.trimmedValue,
address: address.isEmpty ? nil : address.trimmedValue,
streetAddress: streetAddress.isEmpty ? nil : streetAddress.trimmedValue,
city: city.isEmpty ? nil : city.trimmedValue,
state: state.isEmpty ? nil : state.trimmedValue,
zipCode: zipCode.isEmpty ? nil : zipCode.trimmedValue,
stateProvince: stateProvince.isEmpty ? nil : stateProvince.trimmedValue,
postalCode: postalCode.isEmpty ? nil : postalCode.trimmedValue,
rating: nil,
isFavorite: isFavorite,
isActive: true,
notes: notes.isEmpty ? nil : notes.trimmedValue
notes: notes.isEmpty ? nil : notes.trimmedValue,
specialtyIds: selectedSpecialtyIds.isEmpty ? nil : selectedSpecialtyIds.map { KotlinInt(int: $0) }
)
}
@@ -86,20 +89,19 @@ struct ContractorFormState: FormState {
func toUpdateRequest() -> ContractorUpdateRequest {
ContractorUpdateRequest(
name: name.isEmpty ? nil : name.trimmedValue,
residenceId: selectedResidenceId.map { KotlinInt(int: $0) },
company: company.isEmpty ? nil : company.trimmedValue,
phone: phone.isEmpty ? nil : phone.trimmedValue,
email: email.isEmpty ? nil : email.trimmedValue,
secondaryPhone: secondaryPhone.isEmpty ? nil : secondaryPhone.trimmedValue,
specialty: specialty.isEmpty ? nil : specialty.trimmedValue,
licenseNumber: licenseNumber.isEmpty ? nil : licenseNumber.trimmedValue,
website: website.isEmpty ? nil : website.trimmedValue,
address: address.isEmpty ? nil : address.trimmedValue,
streetAddress: streetAddress.isEmpty ? nil : streetAddress.trimmedValue,
city: city.isEmpty ? nil : city.trimmedValue,
state: state.isEmpty ? nil : state.trimmedValue,
zipCode: zipCode.isEmpty ? nil : zipCode.trimmedValue,
stateProvince: stateProvince.isEmpty ? nil : stateProvince.trimmedValue,
postalCode: postalCode.isEmpty ? nil : postalCode.trimmedValue,
rating: nil,
isFavorite: isFavorite.asKotlin,
isActive: nil,
notes: notes.isEmpty ? nil : notes.trimmedValue
notes: notes.isEmpty ? nil : notes.trimmedValue,
specialtyIds: selectedSpecialtyIds.isEmpty ? nil : selectedSpecialtyIds.map { KotlinInt(int: $0) }
)
}
}