Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 75 additions & 11 deletions src/components/new-task-button.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { useState, type ComponentProps } from "react";
import { useNavigate } from "@tanstack/react-router";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { useLiveQuery } from "@tanstack/react-db";
import { useHotkey } from "@tanstack/react-hotkeys";
import { Loader2, Plus } from "lucide-react";
import { ChevronDown, Loader2, Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Kbd } from "@/components/ui/kbd";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { projectsCollection, tasksCollection } from "@/lib/collections";
import { createDesktopRunnerSession } from "@/lib/desktop-runner";
Expand All @@ -24,17 +31,30 @@ export function NewTaskButton({
}: NewTaskButtonProps) {
const navigate = useNavigate();
const [creating, setCreating] = useState(false);
const pathname = useRouterState({ select: (state) => state.location.pathname });
const { data: projects } = useLiveQuery((query) =>
query.from({ p: projectsCollection }).orderBy(({ p }) => p.created_at, "asc"),
);
const { data: tasks } = useLiveQuery((query) =>
query.from({ t: tasksCollection }).orderBy(({ t }) => t.updated_at, "desc"),
);

const [defaultProject] = projects;
const hasProjects = projects.length > 0;
const hasMultipleProjects = projects.length > 1;
const singleProject = hasMultipleProjects ? undefined : defaultProject;
const currentTaskId = pathname.startsWith("/tasks/") ? pathname.split("/")[2] : undefined;
const currentTask = currentTaskId ? tasks.find((task) => task.id === currentTaskId) : undefined;
const currentProject = currentTask?.project_id
? projects.find((project) => project.id === currentTask.project_id)
: undefined;
const hotkeyProject = currentProject ?? defaultProject;

useHotkey(hotkeys.newTask.keys, () => handleNewTask(), { enabled: hotkeyEnabled });
useHotkey(hotkeys.newTask.keys, () => handleNewTask(hotkeyProject), { enabled: hotkeyEnabled });

function handleNewTask() {
const repoUrl = defaultProject?.repo_url;
if (creating || !defaultProject || !repoUrl) {
function handleNewTask(project = defaultProject) {
const repoUrl = project?.repo_url;
if (creating || !project || !repoUrl) {
return;
}

Expand All @@ -45,8 +65,8 @@ export function NewTaskButton({
const taskId = crypto.randomUUID();
tasksCollection.insert({
id: taskId,
organization_id: defaultProject.organization_id,
project_id: defaultProject.id,
organization_id: project.organization_id,
project_id: project.id,
title: taskTitle,
status: "open",
stream_id: null,
Expand Down Expand Up @@ -86,20 +106,64 @@ export function NewTaskButton({
const button = (
<Button
type="button"
disabled={creating || !defaultProject}
onClick={() => handleNewTask()}
disabled={creating || !hasProjects || (!hasMultipleProjects && !singleProject?.repo_url)}
onClick={hasMultipleProjects ? undefined : () => handleNewTask(singleProject)}
{...props}
>
{icon}
{iconOnly ? null : (
<>
<span>New task</span>
<Kbd keys={hotkeys.newTask.keys} />
{hasMultipleProjects ? <ChevronDown className="w-3.5 h-3.5" /> : null}
{hasMultipleProjects ? null : <Kbd keys={hotkeys.newTask.keys} />}
</>
)}
</Button>
);

if (hasMultipleProjects) {
const content = (
<DropdownMenuContent align={iconOnly ? "end" : "start"} className="min-w-56">
<DropdownMenuLabel>Select project</DropdownMenuLabel>
{projects.map((project) => (
<DropdownMenuItem
key={project.id}
disabled={creating || !project.repo_url}
onSelect={() => handleNewTask(project)}
>
{project.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
);

if (iconOnly) {
return (
<DropdownMenu>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent>
<span className="flex items-center gap-2">
{hotkeys.newTask.label}
<Kbd keys={hotkeys.newTask.keys} />
</span>
</TooltipContent>
</Tooltip>
{content}
</DropdownMenu>
);
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>
{content}
</DropdownMenu>
);
}

if (iconOnly) {
return (
<Tooltip>
Expand Down
Loading