diff --git a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx index 786f595d32932..6b44ab0911024 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.stories.tsx @@ -1,13 +1,31 @@ import { chromaticWithTablet } from "testHelpers/chromatic"; -import { MockUserMember, MockUserOwner } from "testHelpers/entities"; +import { + MockUserMember, + MockUserOwner, + MockWorkspace, + MockWorkspaceAppStatus, +} from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { userEvent, within } from "storybook/test"; import { NavbarView } from "./NavbarView"; +const tasksFilter = { + username: MockUserOwner.username, +}; + const meta: Meta = { title: "modules/dashboard/NavbarView", - parameters: { chromatic: chromaticWithTablet, layout: "fullscreen" }, + parameters: { + chromatic: chromaticWithTablet, + layout: "fullscreen", + queries: [ + { + key: ["tasks", tasksFilter], + data: [], + }, + ], + }, component: NavbarView, args: { user: MockUserOwner, @@ -78,3 +96,36 @@ export const CustomLogo: Story = { logo_url: "/icon/github.svg", }, }; + +export const IdleTasks: Story = { + parameters: { + queries: [ + { + key: ["tasks", tasksFilter], + data: [ + { + prompt: "Task 1", + workspace: { + ...MockWorkspace, + latest_app_status: { + ...MockWorkspaceAppStatus, + state: "idle", + }, + }, + }, + { + prompt: "Task 2", + workspace: MockWorkspace, + }, + { + prompt: "Task 3", + workspace: { + ...MockWorkspace, + latest_app_status: MockWorkspaceAppStatus, + }, + }, + ], + }, + ], + }, +}; diff --git a/site/src/modules/dashboard/Navbar/NavbarView.tsx b/site/src/modules/dashboard/Navbar/NavbarView.tsx index 4a2b3027a47dd..0cafaa8fdd46f 100644 --- a/site/src/modules/dashboard/Navbar/NavbarView.tsx +++ b/site/src/modules/dashboard/Navbar/NavbarView.tsx @@ -1,13 +1,21 @@ import { API } from "api/api"; import type * as TypesGen from "api/typesGenerated"; +import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { CoderIcon } from "components/Icons/CoderIcon"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import type { ProxyContextValue } from "contexts/ProxyContext"; import { useWebpushNotifications } from "contexts/useWebpushNotifications"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox"; import type { FC } from "react"; +import { useQuery } from "react-query"; import { NavLink, useLocation } from "react-router"; import { cn } from "utils/cn"; import { DeploymentDropdown } from "./DeploymentDropdown"; @@ -17,7 +25,7 @@ import { UserDropdown } from "./UserDropdown/UserDropdown"; interface NavbarViewProps { logo_url?: string; - user?: TypesGen.User; + user: TypesGen.User; buildInfo?: TypesGen.BuildInfoResponse; supportLinks?: readonly TypesGen.LinkConfig[]; onSignOut: () => void; @@ -60,7 +68,7 @@ export const NavbarView: FC = ({ )} - +
{proxyContextValue && ( @@ -109,16 +117,14 @@ export const NavbarView: FC = ({ } /> - {user && ( -
- -
- )} +
+ +
= ({ interface NavItemsProps { className?: string; + user: TypesGen.User; } -const NavItems: FC = ({ className }) => { +const NavItems: FC = ({ className, user }) => { const location = useLocation(); - const { metadata } = useEmbeddedMetadata(); return ( ); }; + +type TasksNavItemProps = { + user: TypesGen.User; +}; + +const TasksNavItem: FC = ({ user }) => { + const { metadata } = useEmbeddedMetadata(); + const canSeeTasks = Boolean( + metadata["tasks-tab-visible"].value || + process.env.NODE_ENV === "development" || + process.env.STORYBOOK, + ); + const filter = { + username: user.username, + }; + const { data: idleCount } = useQuery({ + queryKey: ["tasks", filter], + queryFn: () => API.experimental.getTasks(filter), + refetchInterval: 1_000 * 60, + enabled: canSeeTasks, + refetchOnWindowFocus: true, + initialData: [], + select: (data) => + data.filter((task) => task.workspace.latest_app_status?.state === "idle") + .length, + }); + + if (!canSeeTasks) { + return null; + } + + return ( + { + return cn(linkStyles.default, { [linkStyles.active]: isActive }); + }} + > + Tasks + {idleCount > 0 && ( + + + + + {idleCount} + + + {idleTasksLabel(idleCount)} + + + )} + + ); +}; + +function idleTasksLabel(count: number) { + return `You have ${count} ${count === 1 ? "task" : "tasks"} waiting for input`; +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy