Add contractors section to residence detail and fix search filtering
- Add GET /contractors/by-residence/:id endpoint integration - Display contractors on residence detail screen (iOS & Android) - Fix contractor search/filter to use client-side filtering - Backend doesn't support search query params, so filter locally 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,10 @@ struct ResidenceDetailView: View {
|
||||
@State private var tasksResponse: TaskColumnsResponse?
|
||||
@State private var isLoadingTasks = false
|
||||
@State private var tasksError: String?
|
||||
|
||||
@State private var contractors: [ContractorSummary] = []
|
||||
@State private var isLoadingContractors = false
|
||||
@State private var contractorsError: String?
|
||||
|
||||
@State private var showAddTask = false
|
||||
@State private var showEditResidence = false
|
||||
@@ -198,9 +202,12 @@ private extension ResidenceDetailView {
|
||||
PropertyHeaderCard(residence: residence)
|
||||
.padding(.horizontal)
|
||||
.padding(.top)
|
||||
|
||||
|
||||
tasksSection
|
||||
.padding(.horizontal)
|
||||
|
||||
contractorsSection
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.bottom)
|
||||
}
|
||||
@@ -226,6 +233,67 @@ private extension ResidenceDetailView {
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var contractorsSection: some View {
|
||||
VStack(alignment: .leading, spacing: AppSpacing.md) {
|
||||
// Section Header
|
||||
HStack(spacing: AppSpacing.sm) {
|
||||
Image(systemName: "person.2.fill")
|
||||
.font(.title2)
|
||||
.foregroundColor(Color.appPrimary)
|
||||
Text("Contractors")
|
||||
.font(.title2.weight(.bold))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
.padding(.top, AppSpacing.sm)
|
||||
|
||||
if isLoadingContractors {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
} else if let error = contractorsError {
|
||||
Text("Error: \(error)")
|
||||
.foregroundColor(Color.appError)
|
||||
.padding()
|
||||
} else if contractors.isEmpty {
|
||||
// Empty state
|
||||
VStack(spacing: AppSpacing.md) {
|
||||
Image(systemName: "person.crop.circle.badge.plus")
|
||||
.font(.system(size: 48))
|
||||
.foregroundColor(Color.appTextSecondary.opacity(0.6))
|
||||
Text("No contractors yet")
|
||||
.font(.headline)
|
||||
.foregroundColor(Color.appTextPrimary)
|
||||
Text("Add contractors from the Contractors tab")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(Color.appTextSecondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(AppSpacing.xl)
|
||||
.background(Color.appBackgroundSecondary)
|
||||
.cornerRadius(AppRadius.lg)
|
||||
} else {
|
||||
// Contractors list
|
||||
VStack(spacing: AppSpacing.sm) {
|
||||
ForEach(contractors, id: \.id) { contractor in
|
||||
NavigationLink(destination: ContractorDetailView(contractorId: contractor.id)) {
|
||||
ContractorCard(
|
||||
contractor: contractor,
|
||||
onToggleFavorite: {
|
||||
// Could implement toggle favorite here if needed
|
||||
}
|
||||
)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Toolbars
|
||||
@@ -299,6 +367,7 @@ private extension ResidenceDetailView {
|
||||
func loadResidenceData() {
|
||||
viewModel.getResidence(id: residenceId)
|
||||
loadResidenceTasks()
|
||||
loadResidenceContractors()
|
||||
}
|
||||
|
||||
func loadResidenceTasks() {
|
||||
@@ -365,6 +434,39 @@ private extension ResidenceDetailView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadResidenceContractors() {
|
||||
guard TokenStorage.shared.getToken() != nil else { return }
|
||||
|
||||
isLoadingContractors = true
|
||||
contractorsError = nil
|
||||
|
||||
Task {
|
||||
do {
|
||||
let result = try await APILayer.shared.getContractorsByResidence(
|
||||
residenceId: Int32(Int(residenceId))
|
||||
)
|
||||
|
||||
await MainActor.run {
|
||||
if let successResult = result as? ApiResultSuccess<NSArray> {
|
||||
self.contractors = (successResult.data as? [ContractorSummary]) ?? []
|
||||
self.isLoadingContractors = false
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.contractorsError = errorResult.message
|
||||
self.isLoadingContractors = false
|
||||
} else {
|
||||
self.contractorsError = "Failed to load contractors"
|
||||
self.isLoadingContractors = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
self.contractorsError = error.localizedDescription
|
||||
self.isLoadingContractors = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TasksSectionContainer: View {
|
||||
|
||||
Reference in New Issue
Block a user