-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: Tidying up kanban board #1900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
836f75f
66fc27d
7039817
aa7b13c
3f4110e
8e57a8e
a0dd8a7
00ecce1
a4f4d6c
39d9033
b13b003
2139966
ac14865
3e78312
f2a2b4f
d0a5bbe
1f63c2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Test files with mock credentials | ||
| *.test.ts | ||
| *.test.tsx | ||
| test_*.py | ||
| tests/ | ||
|
|
||
| # Scripts with checksums/hashes (not secrets) | ||
| apps/frontend/scripts/download-python.cjs | ||
| .github/actions/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ import { | |
| sortableKeyboardCoordinates, | ||
| verticalListSortingStrategy | ||
| } from '@dnd-kit/sortable'; | ||
| import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, RefreshCw, GitPullRequest, X, Settings, ListPlus, ChevronLeft, ChevronRight, ChevronsRight, Lock, Unlock, Trash2 } from 'lucide-react'; | ||
| import { Plus, Inbox, Loader2, Eye, CheckCircle2, Archive, GitPullRequest, X, Settings, ListPlus, ChevronLeft, ChevronRight, ChevronsRight, Lock, Unlock, Trash2 } from 'lucide-react'; | ||
| import { Checkbox } from './ui/checkbox'; | ||
| import { ScrollArea } from './ui/scroll-area'; | ||
| import { Button } from './ui/button'; | ||
|
|
@@ -69,8 +69,6 @@ interface KanbanBoardProps { | |
| tasks: Task[]; | ||
| onTaskClick: (task: Task) => void; | ||
| onNewTaskClick?: () => void; | ||
| onRefresh?: () => void; | ||
| isRefreshing?: boolean; | ||
| } | ||
|
|
||
| interface DroppableColumnProps { | ||
|
|
@@ -84,9 +82,6 @@ interface DroppableColumnProps { | |
| onQueueSettings?: () => void; | ||
| onQueueAll?: () => void; | ||
| maxParallelTasks?: number; | ||
| archivedCount?: number; | ||
| showArchived?: boolean; | ||
| onToggleArchived?: () => void; | ||
| // Selection props for human_review column | ||
| selectedTaskIds?: Set<string>; | ||
| onSelectAll?: () => void; | ||
|
|
@@ -146,9 +141,6 @@ function droppableColumnPropsAreEqual( | |
| if (prevProps.onQueueSettings !== nextProps.onQueueSettings) return false; | ||
| if (prevProps.onQueueAll !== nextProps.onQueueAll) return false; | ||
| if (prevProps.maxParallelTasks !== nextProps.maxParallelTasks) return false; | ||
| if (prevProps.archivedCount !== nextProps.archivedCount) return false; | ||
| if (prevProps.showArchived !== nextProps.showArchived) return false; | ||
| if (prevProps.onToggleArchived !== nextProps.onToggleArchived) return false; | ||
| if (prevProps.onSelectAll !== nextProps.onSelectAll) return false; | ||
| if (prevProps.onDeselectAll !== nextProps.onDeselectAll) return false; | ||
| if (prevProps.onToggleSelect !== nextProps.onToggleSelect) return false; | ||
|
|
@@ -230,11 +222,13 @@ const getEmptyStateContent = (status: TaskStatus, t: (key: string) => string): { | |
| } | ||
| }; | ||
|
|
||
| const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskClick, onStatusChange, isOver, onAddClick, onArchiveAll, onQueueSettings, onQueueAll, maxParallelTasks, archivedCount, showArchived, onToggleArchived, selectedTaskIds, onSelectAll, onDeselectAll, onToggleSelect, isCollapsed, onToggleCollapsed, columnWidth, isResizing, onResizeStart, onResizeEnd, isLocked, onToggleLocked }: DroppableColumnProps) { | ||
| const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskClick, onStatusChange, isOver, onAddClick, onArchiveAll, onQueueSettings, onQueueAll, maxParallelTasks, selectedTaskIds, onSelectAll, onDeselectAll, onToggleSelect, isCollapsed, onToggleCollapsed, columnWidth, isResizing, onResizeStart, onResizeEnd, isLocked, onToggleLocked }: DroppableColumnProps) { | ||
| const { t } = useTranslation(['tasks', 'common']); | ||
| const { setNodeRef } = useDroppable({ | ||
| id: status | ||
| }); | ||
| // Get showArchived from shared context to conditionally show Archive All button | ||
| const { showArchived } = useViewState(); | ||
|
|
||
| // Calculate selection state for this column | ||
| const taskCount = tasks.length; | ||
|
|
@@ -524,33 +518,6 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli | |
| <Archive className="h-4 w-4" /> | ||
| </Button> | ||
| )} | ||
| {status === 'done' && archivedCount !== undefined && archivedCount > 0 && onToggleArchived && ( | ||
| <Tooltip delayDuration={200}> | ||
| <TooltipTrigger asChild> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className={cn( | ||
| 'h-7 w-7 transition-colors relative', | ||
| showArchived | ||
| ? 'text-primary bg-primary/10 hover:bg-primary/20' | ||
| : 'hover:bg-muted-foreground/10 hover:text-muted-foreground' | ||
| )} | ||
| onClick={onToggleArchived} | ||
| aria-pressed={showArchived} | ||
| aria-label={t('common:accessibility.toggleShowArchivedAriaLabel')} | ||
| > | ||
| <Archive className="h-4 w-4" /> | ||
| <span className="absolute -top-1 -right-1 text-[10px] font-medium bg-muted rounded-full min-w-[14px] h-[14px] flex items-center justify-center"> | ||
| {archivedCount} | ||
| </span> | ||
| </Button> | ||
| </TooltipTrigger> | ||
| <TooltipContent> | ||
| {showArchived ? t('common:projectTab.hideArchived') : t('common:projectTab.showArchived')} | ||
| </TooltipContent> | ||
| </Tooltip> | ||
| )} | ||
| </div> | ||
| </div> | ||
|
|
||
|
|
@@ -634,12 +601,13 @@ const DroppableColumn = memo(function DroppableColumn({ status, tasks, onTaskCli | |
| ); | ||
| }, droppableColumnPropsAreEqual); | ||
|
|
||
| export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isRefreshing }: KanbanBoardProps) { | ||
| export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick }: KanbanBoardProps) { | ||
| const { t } = useTranslation(['tasks', 'dialogs', 'common']); | ||
| const { toast } = useToast(); | ||
| // Get showArchived from shared context for cross-view sync with Ideation | ||
| const { showArchived } = useViewState(); | ||
| const [activeTask, setActiveTask] = useState<Task | null>(null); | ||
| const [overColumnId, setOverColumnId] = useState<string | null>(null); | ||
| const { showArchived, toggleShowArchived } = useViewState(); | ||
|
|
||
| // Project store for queue settings | ||
| const projects = useProjectStore((state) => state.projects); | ||
|
|
@@ -700,12 +668,6 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR | |
| error: undefined | ||
| }); | ||
|
|
||
| // Calculate archived count for Done column button | ||
| const archivedCount = useMemo(() => | ||
| tasks.filter(t => t.metadata?.archivedAt).length, | ||
| [tasks] | ||
| ); | ||
|
|
||
| // Calculate collapsed column count for "Expand All" button | ||
| const collapsedColumnCount = useMemo(() => { | ||
| if (!columnPreferences) return 0; | ||
|
|
@@ -1428,36 +1390,20 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR | |
|
|
||
| return ( | ||
| <div className="flex h-full flex-col"> | ||
| {/* Kanban header with refresh button and expand all */} | ||
| {(onRefresh || collapsedColumnCount >= 3) && ( | ||
| {/* Kanban header with expand all button */} | ||
| {collapsedColumnCount >= 3 && ( | ||
| <div className="flex items-center justify-between px-6 pt-4 pb-2"> | ||
| <div className="flex items-center gap-2"> | ||
| {/* Expand All button - appears when 3+ columns are collapsed */} | ||
| {collapsedColumnCount >= 3 && ( | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={handleExpandAll} | ||
| className="gap-2 text-muted-foreground hover:text-foreground" | ||
| > | ||
| <ChevronsRight className="h-4 w-4" /> | ||
| {t('tasks:kanban.expandAll')} | ||
| </Button> | ||
| )} | ||
| </div> | ||
| <div className="flex items-center gap-2"> | ||
| {onRefresh && ( | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={onRefresh} | ||
| disabled={isRefreshing} | ||
| className="gap-2 text-muted-foreground hover:text-foreground" | ||
| > | ||
| <RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} /> | ||
| {isRefreshing ? t('common:buttons.refreshing') : t('tasks:refreshTasks')} | ||
| </Button> | ||
| )} | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| onClick={handleExpandAll} | ||
| className="gap-2 text-muted-foreground hover:text-foreground" | ||
| > | ||
| <ChevronsRight className="h-4 w-4" /> | ||
| {t('tasks:kanban.expandAll')} | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
Comment on lines
+1393
to
1409
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor UX gap: "Expand All" is hidden when fewer than 3 columns are collapsed. With 1 or 2 columns collapsed, 💡 Suggested threshold adjustment- {collapsedColumnCount >= 3 && (
+ {collapsedColumnCount >= 1 && (🤖 Prompt for AI Agents |
||
|
|
@@ -1488,9 +1434,6 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR | |
| } : undefined} | ||
| onArchiveAll={status === 'done' ? handleArchiveAll : undefined} | ||
| maxParallelTasks={status === 'in_progress' ? maxParallelTasks : undefined} | ||
| archivedCount={status === 'done' ? archivedCount : undefined} | ||
| showArchived={status === 'done' ? showArchived : undefined} | ||
| onToggleArchived={status === 'done' ? toggleShowArchived : undefined} | ||
| selectedTaskIds={selectedTaskIds} | ||
| onSelectAll={() => selectAllTasks(status)} | ||
| onDeselectAll={deselectAllTasks} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Use the
@/*path alias instead of a relative path.Line 73 introduces a new relative import in the renderer. Per coding guidelines, all renderer imports should use the
@/*path alias.♻️ Proposed fix
As per coding guidelines: "Use path aliases from
tsconfig.jsonin frontend code:@/*for renderer".📝 Committable suggestion
🤖 Prompt for AI Agents