summaryrefslogtreecommitdiff
path: root/Hutch/Views/Tickets/TicketDetailView.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-13 09:57:23 -0500
committerChristian Cleberg <[email protected]>2026-04-13 09:57:23 -0500
commit0e461a382257979037e8927e5f6b468635b0a7fe (patch)
tree0aa3a19f6f8f966b0e90f4874ed54359a7898107 /Hutch/Views/Tickets/TicketDetailView.swift
parentee9f2904aa319231b7047fca3cb069f0c07019cd (diff)
downloadhutch-0e461a382257979037e8927e5f6b468635b0a7fe.tar.gz
hutch-0e461a382257979037e8927e5f6b468635b0a7fe.tar.bz2
hutch-0e461a382257979037e8927e5f6b468635b0a7fe.zip
feat: add power user actions
Implements: https://todo.sr.ht/~ccleberg/hutch/52 Implements: https://todo.sr.ht/~ccleberg/hutch/53 Implements: https://todo.sr.ht/~ccleberg/hutch/54
Diffstat (limited to 'Hutch/Views/Tickets/TicketDetailView.swift')
-rw-r--r--Hutch/Views/Tickets/TicketDetailView.swift84
1 files changed, 79 insertions, 5 deletions
diff --git a/Hutch/Views/Tickets/TicketDetailView.swift b/Hutch/Views/Tickets/TicketDetailView.swift
index 4bea3ae..f85a0c1 100644
--- a/Hutch/Views/Tickets/TicketDetailView.swift
+++ b/Hutch/Views/Tickets/TicketDetailView.swift
@@ -10,6 +10,7 @@ struct TicketDetailView: View {
@Environment(AppState.self) private var appState
@Environment(\.colorScheme) private var colorScheme
+ @Environment(\.openURL) private var openURL
@State private var viewModel: TicketDetailViewModel?
// Sheet state
@@ -47,7 +48,7 @@ struct TicketDetailView: View {
Image(systemName: "square.and.arrow.up")
}
- if let viewModel, viewModel.ticket != nil, isOwnedByCurrentUser {
+ if let viewModel, viewModel.ticket != nil {
actionsMenu(viewModel)
}
}
@@ -63,7 +64,7 @@ struct TicketDetailView: View {
client: appState.client
)
viewModel = vm
- await vm.loadTicket()
+ await reloadDetail(vm)
}
}
}
@@ -73,6 +74,35 @@ struct TicketDetailView: View {
@ViewBuilder
private func actionsMenu(_ viewModel: TicketDetailViewModel) -> some View {
Menu {
+ if let ticketURL = SRHTWebURL.ticket(ownerUsername: ownerUsername, trackerName: trackerName, ticketId: ticketId) {
+ Button {
+ openURL(ticketURL)
+ } label: {
+ SwiftUI.Label("Open in Browser", systemImage: "safari")
+ }
+
+ Button {
+ appState.copyToPasteboard(ticketURL.absoluteString, label: "ticket URL")
+ } label: {
+ SwiftUI.Label("Copy URL", systemImage: "doc.on.doc")
+ }
+ }
+
+ Button {
+ appState.copyToPasteboard(String(ticketId), label: "ticket ID")
+ } label: {
+ SwiftUI.Label("Copy Ticket ID", systemImage: "number")
+ }
+
+ Button {
+ appState.copyToPasteboard(trackerRid, label: "tracker RID")
+ } label: {
+ SwiftUI.Label("Copy Tracker RID", systemImage: "number")
+ }
+
+ if isOwnedByCurrentUser {
+ Divider()
+
if let ticket = viewModel.ticket {
if ticket.status == .resolved {
Button {
@@ -103,6 +133,7 @@ struct TicketDetailView: View {
} label: {
SwiftUI.Label("Manage Labels", systemImage: "tag")
}
+ }
} label: {
Image(systemName: "ellipsis.circle")
}
@@ -133,7 +164,7 @@ struct TicketDetailView: View {
SRHTErrorStateView(
title: "Couldn't Load Ticket",
message: error,
- retryAction: { await viewModel.loadTicket() }
+ retryAction: { await reloadDetail(viewModel) }
)
} else if let ticket = viewModel.ticket {
ScrollView {
@@ -179,13 +210,19 @@ struct TicketDetailView: View {
.padding(.vertical, 12)
}
- // Comment input
+ if appState.isDebugModeEnabled {
+ Divider()
+ .padding(.vertical, 12)
+
+ debugSection(viewModel: viewModel, ticket: ticket)
+ }
+
commentInput(viewModel)
}
}
.srhtErrorBanner(error: $vm.error)
.refreshable {
- await viewModel.loadTicket()
+ await reloadDetail(viewModel)
}
}
}
@@ -363,6 +400,35 @@ struct TicketDetailView: View {
.padding()
}
+ @ViewBuilder
+ private func debugSection(viewModel: TicketDetailViewModel, ticket: TicketDetail) -> some View {
+ VStack(alignment: .leading, spacing: 12) {
+ Text("Debug")
+ .font(.headline)
+ .padding(.horizontal)
+
+ VStack(alignment: .leading, spacing: 12) {
+ DebugTextBlock(
+ title: "Diagnostics",
+ content: """
+ ticketId: \(ticket.id)
+ trackerId: \(trackerId)
+ trackerRid: \(trackerRid)
+ status: \(ticket.status.rawValue)
+ events: \(viewModel.events.count)
+ url: \(SRHTWebURL.ticket(ownerUsername: ownerUsername, trackerName: trackerName, ticketId: ticket.id)?.absoluteString ?? "unavailable")
+ """
+ )
+
+ if let rawTicketResponse = viewModel.rawTicketResponse {
+ DebugTextBlock(title: "Raw Response", content: rawTicketResponse)
+ }
+ }
+ .padding(.horizontal)
+ .padding(.bottom, 16)
+ }
+ }
+
private func normalizedUsername(_ value: String) -> String {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.hasPrefix("~") ? String(trimmed.dropFirst()) : trimmed
@@ -381,6 +447,14 @@ struct TicketDetailView: View {
}
}
}
+
+ private func reloadDetail(_ viewModel: TicketDetailViewModel) async {
+ if appState.isDebugModeEnabled {
+ await viewModel.loadTicketWithDebugCapture()
+ } else {
+ await viewModel.loadTicket()
+ }
+ }
}
// MARK: - Self-Sizing Markdown Web View