Task attempt dec9197e-0572-4216-ac38-cc3b122df210 - Final changes

This commit is contained in:
Louis Knight-Webb
2025-06-19 21:16:28 -04:00
parent 24454c55dd
commit c1effa517a
6 changed files with 188 additions and 5 deletions

View File

@@ -0,0 +1,32 @@
import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
export function KeyboardShortcutsDemo() {
const shortcuts = useKeyboardShortcuts({
navigate: undefined,
currentPath: '/demo',
hasOpenDialog: false,
closeDialog: () => {},
openCreateTask: () => {}
});
return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Keyboard Shortcuts</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{Object.values(shortcuts).map((shortcut) => (
<div key={shortcut.key} className="flex justify-between items-center">
<span className="text-sm">{shortcut.description}</span>
<kbd className="px-2 py-1 text-xs bg-muted rounded border">
{shortcut.key === 'KeyC' ? 'C' : shortcut.key}
</kbd>
</div>
))}
</div>
</CardContent>
</Card>
);
}

View File

@@ -2,6 +2,7 @@ import * as React from "react"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
import { useDialogKeyboardShortcuts } from "@/lib/keyboard-shortcuts"
const Dialog = React.forwardRef<
HTMLDivElement,
@@ -10,6 +11,13 @@ const Dialog = React.forwardRef<
onOpenChange?: (open: boolean) => void
}
>(({ className, open, onOpenChange, children, ...props }, ref) => {
// Add keyboard shortcut support for closing dialog with Esc
useDialogKeyboardShortcuts(() => {
if (open && onOpenChange) {
onOpenChange(false);
}
});
if (!open) return null
return (

View File

@@ -0,0 +1,112 @@
import { useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
// Define available keyboard shortcuts
export interface KeyboardShortcut {
key: string;
description: string;
action: (context?: KeyboardShortcutContext) => void;
requiresModifier?: boolean;
disabled?: boolean;
}
export interface KeyboardShortcutContext {
navigate?: ReturnType<typeof useNavigate>;
closeDialog?: () => void;
openCreateTask?: () => void;
currentPath?: string;
hasOpenDialog?: boolean;
}
// Centralized shortcut definitions
export const createKeyboardShortcuts = (context: KeyboardShortcutContext): Record<string, KeyboardShortcut> => ({
'Escape': {
key: 'Escape',
description: 'Go back or close dialog',
action: () => {
// If there's an open dialog, close it
if (context.hasOpenDialog && context.closeDialog) {
context.closeDialog();
return;
}
// Otherwise, navigate back
if (context.navigate) {
const currentPath = context.currentPath || window.location.pathname;
// Navigate back based on current path
if (currentPath.includes('/attempts/') && currentPath.includes('/compare')) {
// From compare page, go back to task details
const taskPath = currentPath.split('/attempts/')[0];
context.navigate(taskPath);
} else if (currentPath.includes('/tasks/') && !currentPath.endsWith('/tasks')) {
// From task details, go back to project tasks
const projectPath = currentPath.split('/tasks/')[0] + '/tasks';
context.navigate(projectPath);
} else if (currentPath.includes('/projects/') && currentPath.includes('/tasks')) {
// From project tasks, go back to projects
context.navigate('/projects');
} else if (currentPath !== '/' && currentPath !== '/projects') {
// Default: go to projects page
context.navigate('/projects');
}
}
}
},
'KeyC': {
key: 'c',
description: 'Create new task',
action: () => {
if (context.openCreateTask) {
context.openCreateTask();
}
}
}
});
// Hook to register global keyboard shortcuts
export function useKeyboardShortcuts(context: KeyboardShortcutContext) {
const shortcuts = createKeyboardShortcuts(context);
const handleKeyDown = useCallback((event: KeyboardEvent) => {
// Don't trigger shortcuts when typing in input fields
const target = event.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
// Don't trigger shortcuts when modifier keys are pressed (except for specific shortcuts)
if (event.ctrlKey || event.metaKey || event.altKey) {
return;
}
const shortcut = shortcuts[event.code] || shortcuts[event.key];
if (shortcut && !shortcut.disabled) {
event.preventDefault();
shortcut.action(context);
}
}, [shortcuts, context]);
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [handleKeyDown]);
return shortcuts;
}
// Hook for dialog-specific keyboard shortcuts
export function useDialogKeyboardShortcuts(onClose: () => void) {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.preventDefault();
onClose();
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
}

View File

@@ -5,6 +5,7 @@ import { Card, CardContent } from "@/components/ui/card";
import { ArrowLeft, Plus } from "lucide-react";
import { makeRequest } from "@/lib/api";
import { TaskFormDialog } from "@/components/tasks/TaskFormDialog";
import { useKeyboardShortcuts } from "@/lib/keyboard-shortcuts";
import { TaskKanbanBoard } from "@/components/tasks/TaskKanbanBoard";
import type { TaskStatus, TaskWithAttemptStatus } from "shared/types";
@@ -36,6 +37,21 @@ export function ProjectTasks() {
const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false);
const [editingTask, setEditingTask] = useState<Task | null>(null);
// Define task creation handler
const handleCreateNewTask = () => {
setEditingTask(null);
setIsTaskDialogOpen(true);
};
// Setup keyboard shortcuts
useKeyboardShortcuts({
navigate,
currentPath: `/projects/${projectId}/tasks`,
hasOpenDialog: isTaskDialogOpen,
closeDialog: () => setIsTaskDialogOpen(false),
openCreateTask: handleCreateNewTask
});
useEffect(() => {
if (projectId) {
fetchProject();
@@ -178,11 +194,6 @@ export function ProjectTasks() {
setIsTaskDialogOpen(true);
};
const handleCreateNewTask = () => {
setEditingTask(null);
setIsTaskDialogOpen(true);
};
const handleViewTaskDetails = (task: Task) => {
navigate(`/projects/${projectId}/tasks/${task.id}`);
};

View File

@@ -1,6 +1,7 @@
import { useParams, useNavigate } from 'react-router-dom'
import { ProjectList } from '@/components/projects/project-list'
import { ProjectDetail } from '@/components/projects/project-detail'
import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts'
export function Projects() {
const { projectId } = useParams<{ projectId: string }>()
@@ -10,6 +11,15 @@ export function Projects() {
navigate('/projects')
}
// Setup keyboard shortcuts (only Esc for back navigation, no task creation here)
useKeyboardShortcuts({
navigate,
currentPath: projectId ? `/projects/${projectId}` : '/projects',
hasOpenDialog: false,
closeDialog: () => {},
openCreateTask: () => {} // No-op for projects page
})
if (projectId) {
return (
<ProjectDetail

View File

@@ -14,6 +14,7 @@ import { Separator } from "@/components/ui/separator";
import { ArrowLeft, FileText, Code } from "lucide-react";
import { makeRequest } from "@/lib/api";
import { TaskFormDialog } from "@/components/tasks/TaskFormDialog";
import { useKeyboardShortcuts } from "@/lib/keyboard-shortcuts";
import type {
TaskStatus,
TaskAttempt,
@@ -93,6 +94,15 @@ export function TaskDetailsPage() {
const [isTaskDialogOpen, setIsTaskDialogOpen] = useState(false);
// Setup keyboard shortcuts
useKeyboardShortcuts({
navigate,
currentPath: `/projects/${projectId}/tasks/${taskId}`,
hasOpenDialog: isTaskDialogOpen,
closeDialog: () => setIsTaskDialogOpen(false),
openCreateTask: () => {} // No task creation on task details page
});
// Check if the selected attempt is active (not in a final state)
const isAttemptRunning =
selectedAttempt &&