06/19 Update inspections/issues sent via email, and duplicated issue photos issues

This commit is contained in:
Nguyen Ngo
2026-06-19 17:28:09 -04:00
parent 95c8238c00
commit a57d6f02ba
7 changed files with 1366 additions and 12 deletions
@@ -5,6 +5,7 @@
import SwiftUI
import SwiftData
import MessageUI
struct InspectionHistoryView: View {
@@ -209,7 +210,10 @@ struct HistoryDetailView: View {
let inspection: APIInspectionSummary
@Environment(\.modelContext) private var context
@State private var showReInspect = false
@State private var showReInspect = false
@State private var showMailCompose = false
@State private var isGeneratingPDF = false
@State private var generatedPDFData: Data? = nil
// Local SwiftData copy used only for follow-up sync-back.
// Form data and schema come from the server response directly so
@@ -317,6 +321,20 @@ struct HistoryDetailView: View {
.background(Color(.systemBackground))
.navigationTitle(inspection.templateName)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
Task { await prepareAndShowMail() }
} label: {
if isGeneratingPDF {
ProgressView()
} else {
Label("Share via Email", systemImage: "envelope")
}
}
.disabled(!MFMailComposeViewController.canSendMail() || isGeneratingPDF)
}
}
.onAppear {
loadLocalData()
syncFollowUpToLocalCopy()
@@ -329,6 +347,28 @@ struct HistoryDetailView: View {
parentLocalId: inspection.mobileLocalId
)
}
.sheet(isPresented: $showMailCompose) {
if let pdfData = generatedPDFData {
MailComposeView(
subject: emailSubject,
body: emailBody,
pdfData: pdfData,
pdfFilename: "Inspection_\(inspection.id)_\(inspection.facilityName).pdf"
.replacingOccurrences(of: " ", with: "_")
)
}
}
}
/// Generates the PDF (fetching any server photos over the network),
/// then presents the mail compose sheet with it attached.
/// Photo fetches happen here, off the synchronous PDF drawing pass.
private func prepareAndShowMail() async {
isGeneratingPDF = true
let data = await InspectionPDFGenerator.generate(inspection: inspection)
generatedPDFData = data
isGeneratingPDF = false
showMailCompose = true
}
/// Write the server's follow-up fields back onto the local SwiftData copy
@@ -351,6 +391,48 @@ struct HistoryDetailView: View {
if changed { try? context.save() }
}
// Email content
private var emailSubject: String {
"Inspection Report — \(inspection.facilityName) (\(inspection.templateName))"
}
private var emailBody: String {
var lines: [String] = []
lines.append("INSPECTION REPORT")
lines.append(String(repeating: "=", count: 40))
lines.append("")
lines.append("Template : \(inspection.templateName)")
lines.append("Facility : \(inspection.facilityName)")
if let area = inspection.areaName {
lines.append("Area : \(area)")
}
if let date = inspection.inspectionDateParsed {
lines.append("Date : \(date.formatted(date: .long, time: .shortened))")
}
if let score = inspection.overallScore {
lines.append("Score : \(String(format: "%.1f%%", score))")
}
if inspection.followUpRequired {
lines.append("Follow-up: REQUIRED")
if let note = inspection.followUpNote, !note.isEmpty {
lines.append(" \(note)")
}
}
if !inspection.inspectorNotes.isEmpty {
lines.append("")
lines.append("INSPECTOR NOTES")
lines.append(String(repeating: "-", count: 40))
lines.append(inspection.inspectorNotes)
}
lines.append("")
lines.append("— Sent from JanitorialQC Inspector")
return lines.joined(separator: "\n")
}
// Summary card
private var summaryCard: some View {
@@ -766,3 +848,47 @@ struct PhotoThumbnailView: View {
.buttonStyle(.plain)
}
}
// MARK: - MailComposeView
// UIViewControllerRepresentable wrapping MFMailComposeViewController.
// Attaches a PDF file and pre-fills subject + plain-text body.
// Presents as a sheet; dismissed automatically on send/cancel/save.
struct MailComposeView: UIViewControllerRepresentable {
let subject: String
let body: String
let pdfData: Data
let pdfFilename: String
@Environment(\.dismiss) private var dismiss
func makeUIViewController(context: Context) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.setSubject(subject)
vc.setMessageBody(body, isHTML: false)
vc.addAttachmentData(pdfData,
mimeType: "application/pdf",
fileName: pdfFilename)
vc.mailComposeDelegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: MFMailComposeViewController,
context: Context) {}
func makeCoordinator() -> Coordinator { Coordinator(dismiss: dismiss) }
final class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
private let dismiss: DismissAction
init(dismiss: DismissAction) { self.dismiss = dismiss }
func mailComposeController(
_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?
) {
dismiss()
}
}
}