115 lines
4.4 KiB
Swift
115 lines
4.4 KiB
Swift
// Views/Dashboard/ScheduledInspectionsView.swift
|
|
// ----------------------------------------------
|
|
// Displays the inspector's planned/recurring inspection assignments (phase36),
|
|
// pulled read-only from GET /api/v1/scheduled-inspections by
|
|
// SyncManager.pullScheduledInspections().
|
|
//
|
|
// Two consumers share one ScheduledRow:
|
|
// • ScheduledInspectionsCard — VStack card for the Dashboard ScrollView
|
|
// • MyInspectionsView renders its own "Scheduled" List section inline,
|
|
// reusing ScheduledRow, with the start cover attached to the List.
|
|
// Both self-hide when there are no scheduled inspections and present
|
|
// StartInspectionView (facility + template preselected) when a row is tapped.
|
|
// The schedule lifecycle (fulfil / roll-forward) stays server-driven; tapping
|
|
// "Start" simply seeds the normal new-inspection flow.
|
|
|
|
import SwiftUI
|
|
import SwiftData
|
|
|
|
// MARK: - Shared row
|
|
|
|
struct ScheduledRow: View {
|
|
let schedule: LocalScheduledInspection
|
|
|
|
private var dueText: String {
|
|
if let d = schedule.nextDue {
|
|
return d.formatted(date: .abbreviated, time: .omitted)
|
|
}
|
|
return schedule.dueDateString.isEmpty ? "—" : schedule.dueDateString
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(alignment: .top, spacing: 12) {
|
|
Image(systemName: "calendar.badge.clock")
|
|
.font(.title3)
|
|
.foregroundStyle(schedule.isOverdue ? .red : .blue)
|
|
.padding(.top, 2)
|
|
|
|
VStack(alignment: .leading, spacing: 3) {
|
|
Text(schedule.templateName.isEmpty ? "Inspection" : schedule.templateName)
|
|
.font(.callout.bold())
|
|
Text(schedule.facilityName.isEmpty ? "Facility" : schedule.facilityName)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
|
|
HStack(spacing: 8) {
|
|
if schedule.isOverdue {
|
|
Text("Overdue")
|
|
.font(.caption2.bold())
|
|
.padding(.horizontal, 6).padding(.vertical, 2)
|
|
.background(Color.red.opacity(0.15))
|
|
.foregroundStyle(.red)
|
|
.clipShape(Capsule())
|
|
}
|
|
Text("Due \(dueText)")
|
|
.font(.caption2)
|
|
.foregroundStyle(schedule.isOverdue ? .red : .secondary)
|
|
if !schedule.frequencyLabel.isEmpty {
|
|
Text("· \(schedule.frequencyLabel)")
|
|
.font(.caption2)
|
|
.foregroundStyle(.tertiary)
|
|
}
|
|
}
|
|
}
|
|
|
|
Spacer(minLength: 8)
|
|
|
|
Label("Start", systemImage: "play.fill")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.white)
|
|
.padding(.horizontal, 10).padding(.vertical, 5)
|
|
.background(schedule.isOverdue ? Color.red : Color.blue)
|
|
.clipShape(Capsule())
|
|
}
|
|
.contentShape(Rectangle())
|
|
}
|
|
}
|
|
|
|
// MARK: - Dashboard card (VStack)
|
|
|
|
struct ScheduledInspectionsCard: View {
|
|
@Query(sort: \LocalScheduledInspection.dueDateString, order: .forward)
|
|
private var scheduled: [LocalScheduledInspection]
|
|
|
|
@State private var startTarget: LocalScheduledInspection? = nil
|
|
|
|
var body: some View {
|
|
if !scheduled.isEmpty {
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
Text("SCHEDULED")
|
|
.font(.caption.bold())
|
|
.foregroundStyle(.secondary)
|
|
.tracking(1)
|
|
|
|
ForEach(scheduled) { s in
|
|
Button { startTarget = s } label: {
|
|
ScheduledRow(schedule: s)
|
|
.padding(12)
|
|
.background(Color(.secondarySystemBackground))
|
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|
|
// Cover attached to the stable VStack root (mirrors DraftResumeBanner).
|
|
.fullScreenCover(item: $startTarget) { s in
|
|
StartInspectionView(
|
|
preFillTemplateId: s.templateServerId,
|
|
preFillFacilityId: s.facilityServerId,
|
|
preFillScheduleId: s.serverId
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|