262 lines
9.0 KiB
Swift
262 lines
9.0 KiB
Swift
import SwiftUI
|
|
import ComposeApp
|
|
|
|
struct AddResidenceView: View {
|
|
@Binding var isPresented: Bool
|
|
@StateObject private var viewModel = ResidenceViewModel()
|
|
@StateObject private var lookupsManager = LookupsManager.shared
|
|
@FocusState private var focusedField: Field?
|
|
|
|
// Form fields
|
|
@State private var name: String = ""
|
|
@State private var selectedPropertyType: ResidenceType?
|
|
@State private var streetAddress: String = ""
|
|
@State private var apartmentUnit: String = ""
|
|
@State private var city: String = ""
|
|
@State private var stateProvince: String = ""
|
|
@State private var postalCode: String = ""
|
|
@State private var country: String = "USA"
|
|
@State private var bedrooms: String = ""
|
|
@State private var bathrooms: String = ""
|
|
@State private var squareFootage: String = ""
|
|
@State private var lotSize: String = ""
|
|
@State private var yearBuilt: String = ""
|
|
@State private var description: String = ""
|
|
@State private var isPrimary: Bool = false
|
|
|
|
// Validation errors
|
|
@State private var nameError: String = ""
|
|
@State private var streetAddressError: String = ""
|
|
@State private var cityError: String = ""
|
|
@State private var stateProvinceError: String = ""
|
|
@State private var postalCodeError: String = ""
|
|
|
|
enum Field {
|
|
case name, streetAddress, apartmentUnit, city, stateProvince, postalCode, country
|
|
case bedrooms, bathrooms, squareFootage, lotSize, yearBuilt, description
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
Form {
|
|
Section(header: Text("Property Details")) {
|
|
TextField("Property Name", text: $name)
|
|
.focused($focusedField, equals: .name)
|
|
|
|
if !nameError.isEmpty {
|
|
Text(nameError)
|
|
.font(.caption)
|
|
.foregroundColor(.red)
|
|
}
|
|
|
|
Picker("Property Type", selection: $selectedPropertyType) {
|
|
Text("Select Type").tag(nil as ResidenceType?)
|
|
ForEach(lookupsManager.residenceTypes, id: \.id) { type in
|
|
Text(type.name).tag(type as ResidenceType?)
|
|
}
|
|
}
|
|
}
|
|
|
|
Section(header: Text("Address")) {
|
|
TextField("Street Address", text: $streetAddress)
|
|
.focused($focusedField, equals: .streetAddress)
|
|
|
|
if !streetAddressError.isEmpty {
|
|
Text(streetAddressError)
|
|
.font(.caption)
|
|
.foregroundColor(.red)
|
|
}
|
|
|
|
TextField("Apartment/Unit (optional)", text: $apartmentUnit)
|
|
.focused($focusedField, equals: .apartmentUnit)
|
|
|
|
TextField("City", text: $city)
|
|
.focused($focusedField, equals: .city)
|
|
|
|
if !cityError.isEmpty {
|
|
Text(cityError)
|
|
.font(.caption)
|
|
.foregroundColor(.red)
|
|
}
|
|
|
|
TextField("State/Province", text: $stateProvince)
|
|
.focused($focusedField, equals: .stateProvince)
|
|
|
|
if !stateProvinceError.isEmpty {
|
|
Text(stateProvinceError)
|
|
.font(.caption)
|
|
.foregroundColor(.red)
|
|
}
|
|
|
|
TextField("Postal Code", text: $postalCode)
|
|
.focused($focusedField, equals: .postalCode)
|
|
|
|
if !postalCodeError.isEmpty {
|
|
Text(postalCodeError)
|
|
.font(.caption)
|
|
.foregroundColor(.red)
|
|
}
|
|
|
|
TextField("Country", text: $country)
|
|
.focused($focusedField, equals: .country)
|
|
}
|
|
|
|
Section(header: Text("Property Features")) {
|
|
HStack {
|
|
Text("Bedrooms")
|
|
Spacer()
|
|
TextField("0", text: $bedrooms)
|
|
.keyboardType(.numberPad)
|
|
.multilineTextAlignment(.trailing)
|
|
.frame(width: 60)
|
|
.focused($focusedField, equals: .bedrooms)
|
|
}
|
|
|
|
HStack {
|
|
Text("Bathrooms")
|
|
Spacer()
|
|
TextField("0.0", text: $bathrooms)
|
|
.keyboardType(.decimalPad)
|
|
.multilineTextAlignment(.trailing)
|
|
.frame(width: 60)
|
|
.focused($focusedField, equals: .bathrooms)
|
|
}
|
|
|
|
TextField("Square Footage", text: $squareFootage)
|
|
.keyboardType(.numberPad)
|
|
.focused($focusedField, equals: .squareFootage)
|
|
|
|
TextField("Lot Size (acres)", text: $lotSize)
|
|
.keyboardType(.decimalPad)
|
|
.focused($focusedField, equals: .lotSize)
|
|
|
|
TextField("Year Built", text: $yearBuilt)
|
|
.keyboardType(.numberPad)
|
|
.focused($focusedField, equals: .yearBuilt)
|
|
}
|
|
|
|
Section(header: Text("Additional Details")) {
|
|
TextField("Description (optional)", text: $description, axis: .vertical)
|
|
.lineLimit(3...6)
|
|
|
|
Toggle("Primary Residence", isOn: $isPrimary)
|
|
}
|
|
|
|
if let errorMessage = viewModel.errorMessage {
|
|
Section {
|
|
Text(errorMessage)
|
|
.foregroundColor(.red)
|
|
.font(.caption)
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Add Residence")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
Button("Cancel") {
|
|
isPresented = false
|
|
}
|
|
}
|
|
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button("Save") {
|
|
submitForm()
|
|
}
|
|
.disabled(viewModel.isLoading)
|
|
}
|
|
}
|
|
.onAppear {
|
|
setDefaults()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func setDefaults() {
|
|
// Set default property type if not already set
|
|
if selectedPropertyType == nil && !lookupsManager.residenceTypes.isEmpty {
|
|
selectedPropertyType = lookupsManager.residenceTypes.first
|
|
}
|
|
}
|
|
|
|
private func validateForm() -> Bool {
|
|
var isValid = true
|
|
|
|
if name.isEmpty {
|
|
nameError = "Name is required"
|
|
isValid = false
|
|
} else {
|
|
nameError = ""
|
|
}
|
|
|
|
if streetAddress.isEmpty {
|
|
streetAddressError = "Street address is required"
|
|
isValid = false
|
|
} else {
|
|
streetAddressError = ""
|
|
}
|
|
|
|
if city.isEmpty {
|
|
cityError = "City is required"
|
|
isValid = false
|
|
} else {
|
|
cityError = ""
|
|
}
|
|
|
|
if stateProvince.isEmpty {
|
|
stateProvinceError = "State/Province is required"
|
|
isValid = false
|
|
} else {
|
|
stateProvinceError = ""
|
|
}
|
|
|
|
if postalCode.isEmpty {
|
|
postalCodeError = "Postal code is required"
|
|
isValid = false
|
|
} else {
|
|
postalCodeError = ""
|
|
}
|
|
|
|
return isValid
|
|
}
|
|
|
|
private func submitForm() {
|
|
guard validateForm() else { return }
|
|
guard let propertyType = selectedPropertyType else {
|
|
// Show error
|
|
return
|
|
}
|
|
|
|
let request = ResidenceCreateRequest(
|
|
name: name,
|
|
propertyType: Int32(propertyType.id),
|
|
streetAddress: streetAddress,
|
|
apartmentUnit: apartmentUnit.isEmpty ? nil : apartmentUnit,
|
|
city: city,
|
|
stateProvince: stateProvince,
|
|
postalCode: postalCode,
|
|
country: country,
|
|
bedrooms: Int32(bedrooms) as? KotlinInt,
|
|
bathrooms: Float(bathrooms) as? KotlinFloat,
|
|
squareFootage: Int32(squareFootage) as? KotlinInt,
|
|
lotSize: Float(lotSize) as? KotlinFloat,
|
|
yearBuilt: Int32(yearBuilt) as? KotlinInt,
|
|
description: description.isEmpty ? nil : description,
|
|
purchaseDate: nil,
|
|
purchasePrice: nil,
|
|
isPrimary: isPrimary
|
|
)
|
|
|
|
viewModel.createResidence(request: request) { success in
|
|
if success {
|
|
isPresented = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#Preview {
|
|
AddResidenceView(isPresented: .constant(true))
|
|
}
|