91 lines
3.5 KiB
Swift
91 lines
3.5 KiB
Swift
// Views/Dashboard/NotificationsView.swift
|
|
import SwiftUI
|
|
import SwiftData
|
|
import MessageUI
|
|
|
|
// MARK: - Notifications Inbox
|
|
// Shows the most recent notifications fetched during polling.
|
|
// Notifications are already marked read on the server by pollNotifications().
|
|
|
|
struct NotificationsView: View {
|
|
|
|
@EnvironmentObject private var sync: SyncManager
|
|
|
|
var body: some View {
|
|
Group {
|
|
if sync.recentNotifications.isEmpty {
|
|
if !sync.isOnline {
|
|
ContentUnavailableView(
|
|
"Offline",
|
|
systemImage: "wifi.slash",
|
|
description: Text("Notifications are delivered when you go online.")
|
|
)
|
|
} else {
|
|
ContentUnavailableView(
|
|
"No Notifications",
|
|
systemImage: "bell.slash",
|
|
description: Text("You\'re all caught up.")
|
|
)
|
|
}
|
|
} else {
|
|
List(sync.recentNotifications) { notif in
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
HStack(alignment: .top) {
|
|
Image(systemName: iconName(for: notif.eventType))
|
|
.foregroundStyle(iconColor(for: notif.eventType))
|
|
.frame(width: 24)
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(notif.title)
|
|
.font(.callout.bold())
|
|
.lineLimit(2)
|
|
Text(notif.body)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
.lineLimit(3)
|
|
}
|
|
}
|
|
if let date = SyncManager.isoFormatter.date(from: notif.createdAt) {
|
|
Text(date.formatted(.relative(presentation: .named)))
|
|
.font(.caption2)
|
|
.foregroundStyle(.tertiary)
|
|
}
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Notifications")
|
|
.navigationBarTitleDisplayMode(.large)
|
|
.onAppear {
|
|
sync.markNotificationsViewed()
|
|
}
|
|
.refreshable {
|
|
await sync.pollNotifications()
|
|
sync.markNotificationsViewed()
|
|
}
|
|
}
|
|
|
|
private func iconName(for eventType: String?) -> String {
|
|
switch eventType {
|
|
case "inspection_completed": return "checkmark.circle.fill"
|
|
case "issue_flagged": return "exclamationmark.triangle.fill"
|
|
case "issue_resolved": return "checkmark.seal.fill"
|
|
case "sla_alert": return "clock.badge.exclamationmark"
|
|
case "follow_up_required": return "exclamationmark.arrow.circlepath"
|
|
default: return "bell.fill"
|
|
}
|
|
}
|
|
|
|
private func iconColor(for eventType: String?) -> Color {
|
|
switch eventType {
|
|
case "inspection_completed": return .green
|
|
case "issue_flagged": return .orange
|
|
case "issue_resolved": return .green
|
|
case "sla_alert": return .red
|
|
case "follow_up_required": return .orange
|
|
default: return .blue
|
|
}
|
|
}
|
|
}
|
|
|