feat(kanban): add refresh button to manually reload tasks (#548)

* feat(kanban): add refresh button to manually reload tasks

- Add Refresh button in Kanban Board header
- Show spinning animation while refreshing
- Add handleRefreshTasks handler in App component
- Pass onRefresh and isRefreshing props to KanbanBoard

This helps when the UI doesn't update or when tasks are stuck,
giving users a way to manually refresh task state.

* fix: use ml-auto instead of justify-between for flexible layout

Apply code review suggestion to improve layout flexibility by using
ml-auto on the right-side controls instead of justify-between on parent.
This commit is contained in:
Adryan Serage
2026-01-02 02:30:29 -05:00
committed by GitHub
parent 8db71f3dfb
commit 252242f940
2 changed files with 33 additions and 3 deletions
+14
View File
@@ -85,6 +85,7 @@ export function App() {
const [settingsInitialProjectSection, setSettingsInitialProjectSection] = useState<ProjectSettingsSection | undefined>(undefined);
const [activeView, setActiveView] = useState<SidebarView>('kanban');
const [isOnboardingWizardOpen, setIsOnboardingWizardOpen] = useState(false);
const [isRefreshingTasks, setIsRefreshingTasks] = useState(false);
// Initialize dialog state
const [showInitDialog, setShowInitDialog] = useState(false);
@@ -373,6 +374,17 @@ export function App() {
setSelectedTask(task);
};
const handleRefreshTasks = async () => {
const currentProjectId = activeProjectId || selectedProjectId;
if (!currentProjectId) return;
setIsRefreshingTasks(true);
try {
await loadTasks(currentProjectId);
} finally {
setIsRefreshingTasks(false);
}
};
const handleCloseTaskDetail = () => {
setSelectedTask(null);
};
@@ -626,6 +638,8 @@ export function App() {
tasks={tasks}
onTaskClick={handleTaskClick}
onNewTaskClick={() => setIsNewTaskDialogOpen(true)}
onRefresh={handleRefreshTasks}
isRefreshing={isRefreshingTasks}
/>
)}
{/* TerminalGrid is always mounted but hidden when not active to preserve terminal state */}
@@ -17,7 +17,7 @@ import {
sortableKeyboardCoordinates,
verticalListSortingStrategy
} from '@dnd-kit/sortable';
import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive } from 'lucide-react';
import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, RefreshCw } from 'lucide-react';
import { ScrollArea } from './ui/scroll-area';
import { Button } from './ui/button';
import { Checkbox } from './ui/checkbox';
@@ -33,6 +33,8 @@ interface KanbanBoardProps {
tasks: Task[];
onTaskClick: (task: Task) => void;
onNewTaskClick?: () => void;
onRefresh?: () => void;
isRefreshing?: boolean;
}
interface DroppableColumnProps {
@@ -209,7 +211,7 @@ function DroppableColumn({ status, tasks, onTaskClick, isOver, onAddClick, onArc
);
}
export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick }: KanbanBoardProps) {
export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isRefreshing }: KanbanBoardProps) {
const [activeTask, setActiveTask] = useState<Task | null>(null);
const [overColumnId, setOverColumnId] = useState<string | null>(null);
const [showArchived, setShowArchived] = useState(false);
@@ -337,8 +339,22 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick }: KanbanBoardP
return (
<div className="flex h-full flex-col">
{/* Kanban header with filters */}
<div className="flex items-center justify-end px-6 py-3 border-b border-border/50">
<div className="flex items-center px-6 py-3 border-b border-border/50">
<div className="flex items-center gap-2">
{onRefresh && (
<Button
variant="ghost"
size="sm"
onClick={onRefresh}
disabled={isRefreshing}
className="gap-2"
>
<RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} />
{isRefreshing ? 'Refreshing...' : 'Refresh'}
</Button>
)}
</div>
<div className="flex items-center gap-2 ml-auto">
<Checkbox
id="showArchived"
checked={showArchived}