diff options
| author | Christian Cleberg <[email protected]> | 2026-04-12 19:16:48 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-12 19:16:48 -0500 |
| commit | 16b56544ad5bfe39e1f58d025646988c34717033 (patch) | |
| tree | b3ac0ecd04ac4f407ebeb74d55a90c193bf10109 | |
| parent | d4757fd9f3aba663df967ae7cadb7f74c3d7acb4 (diff) | |
| download | hutch-16b56544ad5bfe39e1f58d025646988c34717033.tar.gz hutch-16b56544ad5bfe39e1f58d025646988c34717033.tar.bz2 hutch-16b56544ad5bfe39e1f58d025646988c34717033.zip | |
feat: improve ticket list actions and metadata visibility
Implements: https://todo.sr.ht/~ccleberg/hutch/20
Implements: https://todo.sr.ht/~ccleberg/hutch/21
| -rw-r--r-- | Hutch/Views/Tickets/TicketListView.swift | 119 |
1 files changed, 71 insertions, 48 deletions
diff --git a/Hutch/Views/Tickets/TicketListView.swift b/Hutch/Views/Tickets/TicketListView.swift index 21e806c..8b5fc03 100644 --- a/Hutch/Views/Tickets/TicketListView.swift +++ b/Hutch/Views/Tickets/TicketListView.swift @@ -205,12 +205,13 @@ struct TicketListView: View { } .swipeActions(edge: .leading, allowsFullSwipe: true) { if swipeActionsEnabled { - ticketLeadingSwipeAction(ticket, viewModel: viewModel) + ticketAssignSwipeAction(ticket, viewModel: viewModel) } } .swipeActions(edge: .trailing, allowsFullSwipe: false) { if swipeActionsEnabled { - ticketTrailingSwipeActions(ticket, viewModel: viewModel) + ticketStatusSwipeAction(ticket, viewModel: viewModel) + ticketLabelSwipeAction(ticket, viewModel: viewModel) } } .task { @@ -294,52 +295,10 @@ struct TicketListView: View { } @ViewBuilder - private func ticketLeadingSwipeAction( + private func ticketAssignSwipeAction( _ ticket: TicketSummary, viewModel: TicketListViewModel ) -> some View { - if ticket.status.isOpen { - Button { - Task { - await resolveTicket(ticket, viewModel: viewModel) - } - } label: { - Label("Resolve", systemImage: "checkmark.circle") - } - .tint(.green) - } else { - Button { - Task { - await reopenTicket(ticket, viewModel: viewModel) - } - } label: { - Label("Reopen", systemImage: "arrow.uturn.backward") - } - .tint(.blue) - } - } - - private func resolveTicket(_ ticket: TicketSummary, viewModel: TicketListViewModel) async { - await viewModel.resolveTicket(ticket) - } - - private func reopenTicket(_ ticket: TicketSummary, viewModel: TicketListViewModel) async { - await viewModel.reopenTicket(ticket) - } - - @ViewBuilder - private func ticketTrailingSwipeActions( - _ ticket: TicketSummary, - viewModel: TicketListViewModel - ) -> some View { - Button { - labelEditorTicket = LabelEditorTicket(id: ticket.id) - Task { await viewModel.loadTrackerLabels() } - } label: { - Label("Edit Labels", systemImage: "tag") - } - .tint(.purple) - if let currentUser = appState.currentUser { let isAssigned = ticket.assignees.contains { assignee in matchesAssignee(assignee, user: currentUser) @@ -367,6 +326,42 @@ struct TicketListView: View { } } + @ViewBuilder + private func ticketStatusSwipeAction( + _ ticket: TicketSummary, + viewModel: TicketListViewModel + ) -> some View { + if ticket.status.isOpen { + Button { + Task { await viewModel.resolveTicket(ticket) } + } label: { + Label("Close", systemImage: "xmark.circle") + } + .tint(.red) + } else { + Button { + Task { await viewModel.reopenTicket(ticket) } + } label: { + Label("Reopen", systemImage: "arrow.uturn.backward") + } + .tint(.blue) + } + } + + @ViewBuilder + private func ticketLabelSwipeAction( + _ ticket: TicketSummary, + viewModel: TicketListViewModel + ) -> some View { + Button { + labelEditorTicket = LabelEditorTicket(id: ticket.id) + Task { await viewModel.loadTrackerLabels() } + } label: { + Label("Labels", systemImage: "tag") + } + .tint(.purple) + } + private func matchesAssignee(_ entity: Entity, user: User) -> Bool { let assigneeCanonical = normalizedCanonicalName(entity.canonicalName) let userCanonical = normalizedCanonicalName(user.canonicalName) @@ -546,6 +541,8 @@ private struct TicketRowView: View { } Spacer() + + TicketStatusBadge(status: ticket.status) } HStack(spacing: 8) { @@ -584,6 +581,32 @@ private struct TicketRowView: View { } } +// MARK: - Ticket Status Badge + +private struct TicketStatusBadge: View { + let status: TicketStatus + + var body: some View { + Text(status.displayName) + .font(.caption2.weight(.semibold)) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .foregroundStyle(color) + .background(color.opacity(0.12)) + .clipShape(Capsule()) + } + + private var color: Color { + switch status { + case .reported: .gray + case .confirmed: .blue + case .inProgress: .yellow + case .pending: .orange + case .resolved: .green + } + } +} + // MARK: - Ticket Status Icon struct TicketStatusIcon: View { @@ -622,9 +645,9 @@ struct LabelPill: View { var body: some View { Text(label.name) - .font(.caption2.weight(.medium)) - .padding(.horizontal, 6) - .padding(.vertical, 2) + .font(.caption.weight(.medium)) + .padding(.horizontal, 8) + .padding(.vertical, 3) .background(backgroundColor) .foregroundStyle(foregroundColor) .clipShape(Capsule()) |
