diff options
Diffstat (limited to 'Hutch/Views/Tickets/TicketDetailView.swift')
| -rw-r--r-- | Hutch/Views/Tickets/TicketDetailView.swift | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/Hutch/Views/Tickets/TicketDetailView.swift b/Hutch/Views/Tickets/TicketDetailView.swift index 92263f6..b114fa6 100644 --- a/Hutch/Views/Tickets/TicketDetailView.swift +++ b/Hutch/Views/Tickets/TicketDetailView.swift @@ -11,12 +11,15 @@ struct TicketDetailView: View { @Environment(AppState.self) private var appState @Environment(\.colorScheme) private var colorScheme @Environment(\.openURL) private var openURL + @Environment(\.dismiss) private var dismiss @State private var viewModel: TicketDetailViewModel? // Sheet state @State private var showResolveSheet = false @State private var showAssignSheet = false @State private var showLabelsSheet = false + @State private var showEditSheet = false + @State private var showDeleteConfirmation = false @State private var isOpeningTracker = false // Comment composer mode @@ -100,9 +103,30 @@ struct TicketDetailView: View { SwiftUI.Label("Copy Tracker RID", systemImage: "number") } + Divider() + + Button { + Task { await viewModel.toggleSubscription() } + } label: { + if viewModel.isSubscribed { + SwiftUI.Label("Unsubscribe", systemImage: "bell.slash") + } else { + SwiftUI.Label("Subscribe", systemImage: "bell") + } + } + .disabled(viewModel.isPerformingAction) + if isOwnedByCurrentUser { Divider() + if viewModel.ticket != nil { + Button { + showEditSheet = true + } label: { + SwiftUI.Label("Edit Ticket", systemImage: "square.and.pencil") + } + } + if let ticket = viewModel.ticket { if ticket.status == .resolved { Button { @@ -133,6 +157,14 @@ struct TicketDetailView: View { } label: { SwiftUI.Label("Manage Labels", systemImage: "tag") } + + Divider() + + Button(role: .destructive) { + showDeleteConfirmation = true + } label: { + SwiftUI.Label("Delete Ticket", systemImage: "trash") + } } } label: { Image(systemName: "ellipsis.circle") @@ -150,6 +182,32 @@ struct TicketDetailView: View { LabelsSheet(viewModel: viewModel, isPresented: $showLabelsSheet) .presentationDetents([.medium]) } + .sheet(isPresented: $showEditSheet) { + if let ticket = viewModel.ticket { + EditTicketSheet( + viewModel: viewModel, + isPresented: $showEditSheet, + initialSubject: ticket.title, + initialBody: ticket.description ?? "" + ) + } + } + .confirmationDialog( + "Delete Ticket #\(ticketId)?", + isPresented: $showDeleteConfirmation, + titleVisibility: .visible + ) { + Button("Delete Ticket", role: .destructive) { + Task { + if await viewModel.deleteTicket() { + dismiss() + } + } + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This permanently deletes the ticket and its comments. This cannot be undone.") + } } // MARK: - Detail Content @@ -724,6 +782,81 @@ private struct EventRow: View { // MARK: - Resolve Sheet +private struct EditTicketSheet: View { + let viewModel: TicketDetailViewModel + @Binding var isPresented: Bool + let initialSubject: String + let initialBody: String + + @State private var subject: String + @State private var ticketBody: String + + init( + viewModel: TicketDetailViewModel, + isPresented: Binding<Bool>, + initialSubject: String, + initialBody: String + ) { + self.viewModel = viewModel + _isPresented = isPresented + self.initialSubject = initialSubject + self.initialBody = initialBody + _subject = State(initialValue: initialSubject) + _ticketBody = State(initialValue: initialBody) + } + + private var trimmedSubject: String { + subject.trimmingCharacters(in: .whitespacesAndNewlines) + } + + private var hasChanges: Bool { + trimmedSubject != initialSubject + || ticketBody.trimmingCharacters(in: .whitespacesAndNewlines) != initialBody + } + + var body: some View { + NavigationStack { + Form { + Section("Subject") { + TextField("Subject", text: $subject, axis: .vertical) + .themedRow() + } + + Section("Description") { + TextField("Description", text: $ticketBody, axis: .vertical) + .lineLimit(5...15) + .themedRow() + } + } + .themedList() + .navigationTitle("Edit Ticket") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { isPresented = false } + } + ToolbarItem(placement: .confirmationAction) { + Button("Save") { + Task { + if await viewModel.updateTicket(subject: subject, body: ticketBody) { + isPresented = false + } + } + } + .disabled(viewModel.isPerformingAction || trimmedSubject.isEmpty || !hasChanges) + } + } + .overlay { + if viewModel.isPerformingAction { + ProgressView() + } + } + } + } +} + +// MARK: - Resolve Sheet + private struct ResolveSheet: View { let viewModel: TicketDetailViewModel @Binding var isPresented: Bool |
