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
@@ -13,6 +13,7 @@
import SwiftUI
import SwiftData
import Combine
import MessageUI
// MARK: - SidebarTab
@@ -1029,6 +1030,10 @@ struct IssueDetailView: View {
@State private var newCommentText = ""
@State private var isPostingComment = false
@State private var commentError: String?
// Email PDF
@State private var isGeneratingPDF = false
@State private var showMailCompose = false
@State private var generatedPDFData: Data? = nil
private var facilityName: String {
let id = issue.facilityServerId
@@ -1263,12 +1268,73 @@ struct IssueDetailView: View {
}
.navigationTitle("Issue Detail")
.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)
}
}
.sheet(isPresented: $showMailCompose) {
if let pdfData = generatedPDFData {
MailComposeView(
subject: "Issue Report — Issue #\(issue.serverId ?? 0) (\(facilityName))",
body: issueEmailBody,
pdfData: pdfData,
pdfFilename: "Issue_\(issue.serverId ?? 0)_\(facilityName).pdf"
.replacingOccurrences(of: " ", with: "_")
)
}
}
.task {
await refreshStatusFromServer()
await loadComments()
}
}
/// Generates the issue PDF (re-encoding photos to keep file size minimal,
/// fetching server photos over the network if synced), then presents the
/// mail compose sheet with it attached.
private func prepareAndShowMail() async {
isGeneratingPDF = true
let data = await IssuePDFGenerator.generate(issue: issue, facilityName: facilityName)
generatedPDFData = data
isGeneratingPDF = false
showMailCompose = true
}
private var issueEmailBody: String {
var lines: [String] = []
lines.append("ISSUE REPORT — Issue #\(issue.serverId ?? 0)")
lines.append(String(repeating: "=", count: 40))
lines.append("")
lines.append("Severity : \(issue.severity.capitalized)")
lines.append("Status : \(statusLabel(for: issue.issueStatus))")
lines.append("Facility : \(facilityName)")
if let area = issue.areaNameCache, !area.isEmpty {
lines.append("Area : \(area)")
}
if let assignee = issue.assignedToName, !assignee.isEmpty {
lines.append("Assigned : \(assignee)")
}
let reportDate = issue.serverReportedAt ?? issue.createdAt
lines.append("Reported : \(reportDate.formatted(date: .long, time: .shortened))")
lines.append("")
lines.append("DESCRIPTION")
lines.append(String(repeating: "-", count: 40))
lines.append(issue.issueDescription)
lines.append("")
lines.append("— Sent from JanitorialQC Inspector")
return lines.joined(separator: "\n")
}
// Fetch fresh status from server
private func refreshStatusFromServer() async {