- 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>
103 lines
3.7 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|