06/19 Update inspections/issues sent via email, and duplicated issue photos issues
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user