diff options
| author | Christian Cleberg <[email protected]> | 2026-03-19 15:17:38 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-03-19 15:17:38 -0500 |
| commit | 6ba4e967d5dfb5d3c7bb97a0f2662f3180595563 (patch) | |
| tree | 572bef546fcca19ad37bc1aa7cfb153eb2bf8eb7 /Hutch/Views/Home/HomeView.swift | |
| parent | 1e3c748119c6e9eec27f02146f17ea0302ff648a (diff) | |
| download | hutch-6ba4e967d5dfb5d3c7bb97a0f2662f3180595563.tar.gz hutch-6ba4e967d5dfb5d3c7bb97a0f2662f3180595563.tar.bz2 hutch-6ba4e967d5dfb5d3c7bb97a0f2662f3180595563.zip | |
feat: implement support for projects, lists, and pastes
Diffstat (limited to 'Hutch/Views/Home/HomeView.swift')
| -rw-r--r-- | Hutch/Views/Home/HomeView.swift | 109 |
1 files changed, 105 insertions, 4 deletions
diff --git a/Hutch/Views/Home/HomeView.swift b/Hutch/Views/Home/HomeView.swift index 1e98039..55ec41e 100644 --- a/Hutch/Views/Home/HomeView.swift +++ b/Hutch/Views/Home/HomeView.swift @@ -4,6 +4,7 @@ struct HomeView: View { @Environment(AppState.self) private var appState @State private var viewModel: HomeViewModel? private let previewLimit = 4 + private let projectPreviewLimit = 3 var body: some View { Group { @@ -14,6 +15,15 @@ struct HomeView: View { } } .navigationTitle("Home") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + NavigationLink { + InboxView() + } label: { + HomeInboxToolbarIcon(hasUnreadThreads: viewModel?.hasUnreadInboxThreads == true) + } + } + } .task { if viewModel == nil, let currentUser = appState.currentUser { let vm = HomeViewModel(currentUser: currentUser, client: appState.client) @@ -26,16 +36,17 @@ struct HomeView: View { @ViewBuilder private func content(_ viewModel: HomeViewModel) -> some View { List { + projectsSection(viewModel) assignedTicketsSection(viewModel) recentBuildsSection(viewModel) } .listStyle(.insetGrouped) .overlay { - if viewModel.isLoadingAssignedTickets && viewModel.isLoadingRecentBuilds && - viewModel.assignedTickets.isEmpty && viewModel.recentBuilds.isEmpty { + if viewModel.isLoadingProjects && viewModel.isLoadingAssignedTickets && viewModel.isLoadingRecentBuilds && + viewModel.projects.isEmpty && viewModel.assignedTickets.isEmpty && viewModel.recentBuilds.isEmpty { SRHTLoadingStateView(message: "Loading Home…") - } else if !viewModel.isLoadingAssignedTickets && !viewModel.isLoadingRecentBuilds && - viewModel.assignedTickets.isEmpty && viewModel.recentBuilds.isEmpty && + } else if !viewModel.isLoadingProjects && !viewModel.isLoadingAssignedTickets && !viewModel.isLoadingRecentBuilds && + viewModel.projects.isEmpty && viewModel.assignedTickets.isEmpty && viewModel.recentBuilds.isEmpty && viewModel.assignedTicketsError == nil && viewModel.recentBuildsError == nil { ContentUnavailableView( "All Clear", @@ -50,6 +61,25 @@ struct HomeView: View { } @ViewBuilder + private func projectsSection(_ viewModel: HomeViewModel) -> some View { + if !viewModel.projects.isEmpty { + Section { + ForEach(viewModel.projects.prefix(projectPreviewLimit)) { project in + NavigationLink { + ProjectDetailView(project: project) + } label: { + HomeProjectRow(project: project) + } + } + } header: { + HomeSectionHeader("Projects") { + HomeProjectsListView(viewModel: viewModel) + } + } + } + } + + @ViewBuilder private func assignedTicketsSection(_ viewModel: HomeViewModel) -> some View { Section { if viewModel.isLoadingAssignedTickets && viewModel.assignedTickets.isEmpty { @@ -123,6 +153,77 @@ struct HomeView: View { } +private struct HomeInboxToolbarIcon: View { + let hasUnreadThreads: Bool + + var body: some View { + ZStack(alignment: .topTrailing) { + Image(systemName: hasUnreadThreads ? "tray.fill" : "tray") + + if hasUnreadThreads { + Circle() + .fill(.blue) + .frame(width: 9, height: 9) + .offset(x: 4, y: -2) + } + } + .accessibilityLabel(hasUnreadThreads ? "Inbox, unread messages" : "Inbox") + } +} + +private struct HomeProjectRow: View { + let project: Project + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + Text(project.name) + .font(.subheadline.weight(.medium)) + .lineLimit(1) + + if let description = project.description, !description.isEmpty { + Text(description) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + + if let summary = project.resourceSummary { + Text(summary) + .font(.caption) + .foregroundStyle(.tertiary) + .lineLimit(1) + } + } + .padding(.vertical, 2) + } +} + +private struct HomeProjectsListView: View { + let viewModel: HomeViewModel + + var body: some View { + List { + ForEach(viewModel.projects) { project in + NavigationLink { + ProjectDetailView(project: project) + } label: { + HomeProjectRow(project: project) + } + } + } + .navigationTitle("Projects") + .navigationBarTitleDisplayMode(.inline) + .refreshable { + await viewModel.loadDashboard() + } + .overlay { + if viewModel.isLoadingProjects && viewModel.projects.isEmpty { + SRHTLoadingStateView(message: "Loading projects…") + } + } + } +} + private struct HomeBuildRow: View { let build: HomeBuildItem |
