Files
honeyDueKMP/iosApp/iosApp/Subviews/Task/CompletionCardView.swift
Trey t c07821711f Add centralized DateUtils and enhance contractor detail views
- Add DateUtils.kt for shared Kotlin date formatting with formatDate,
  formatDateMedium, formatDateTime, formatRelativeDate, and isOverdue
- Add DateUtils.swift for iOS with matching date formatting functions
- Enhance ContractorDetailScreen (Android) with quick action buttons
  (call, email, website, directions), clickable contact rows, residence
  association, statistics section, and metadata
- Enhance ContractorDetailView (iOS) with same features, refactored into
  smaller @ViewBuilder functions to fix Swift compiler type-check timeout
- Fix empty string handling in iOS - check !isEmpty in addition to != nil
  for optional fields like phone, email, website, address
- Update various task and document views to use centralized DateUtils

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 14:08:45 -06:00

103 lines
3.7 KiB
Swift

import SwiftUI
import ComposeApp
struct CompletionCardView: View {
let completion: TaskCompletionResponse
@State private var showPhotoSheet = false
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(DateUtils.formatDateMedium(completion.completionDate))
.font(.caption)
.fontWeight(.semibold)
.foregroundColor(Color.appPrimary)
Spacer()
if let rating = completion.rating {
HStack(spacing: 2) {
Image(systemName: "star.fill")
.font(.caption2)
Text("\(rating)")
.font(.caption)
.fontWeight(.bold)
}
.foregroundColor(Color.appAccent)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.appAccent.opacity(0.1))
.cornerRadius(6)
}
}
// Display contractor or manual entry
if let contractorDetails = completion.contractorDetails {
HStack(alignment: .top, spacing: 6) {
Image(systemName: "wrench.and.screwdriver")
.font(.caption2)
.foregroundColor(Color.appPrimary)
VStack(alignment: .leading, spacing: 2) {
Text("By: \(contractorDetails.name)")
.font(.caption2)
.fontWeight(.medium)
.foregroundColor(Color.appTextPrimary)
if let company = contractorDetails.company {
Text(company)
.font(.caption2)
.foregroundColor(Color.appTextSecondary)
}
}
}
} else if let completedBy = completion.completedByName {
Text("By: \(completedBy)")
.font(.caption2)
.foregroundColor(Color.appTextSecondary)
}
if let cost = completion.actualCost {
Text("Cost: $\(cost)")
.font(.caption2)
.foregroundColor(Color.appPrimary)
.fontWeight(.medium)
}
if !completion.notes.isEmpty {
Text(completion.notes)
.font(.caption2)
.foregroundColor(Color.appTextSecondary)
.lineLimit(2)
}
// Show button to view photos if images exist
if !completion.images.isEmpty {
let images = completion.images
Button(action: {
showPhotoSheet = true
}) {
HStack {
Image(systemName: "photo.on.rectangle")
.font(.caption)
Text("View Photos (\(images.count))")
.font(.caption)
.fontWeight(.semibold)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
.background(Color.appPrimary.opacity(0.1))
.foregroundColor(Color.appPrimary)
.cornerRadius(8)
}
}
}
.padding(12)
.background(Color.appBackgroundSecondary.opacity(0.5))
.cornerRadius(8)
.sheet(isPresented: $showPhotoSheet) {
PhotoViewerSheet(images: completion.images)
}
}
}