From ac3f746ba327a4f630bc6eee9fc27b9bd6da137c Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Thu, 12 Mar 2026 00:16:00 +0800 Subject: [PATCH] Fix QA findings: combine todo queries, remove dead prop, add aria-labels - Merge total_todos and total_incomplete_todos into single DB query (W-04) - Remove unused `days` prop from UpcomingWidget interface (W-03) - Add aria-label to focus/show-past toggle buttons (S-08) - Add zero-duration event guard in CalendarWidget progress calc (S-07) - Combine duplicate date-utils imports (S-01) Co-Authored-By: Claude Opus 4.6 --- backend/app/routers/dashboard.py | 24 ++++++++----------- .../components/dashboard/CalendarWidget.tsx | 1 + .../components/dashboard/DashboardPage.tsx | 2 +- .../components/dashboard/UpcomingWidget.tsx | 6 ++--- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/backend/app/routers/dashboard.py b/backend/app/routers/dashboard.py index c83ce7b..3e6f076 100644 --- a/backend/app/routers/dashboard.py +++ b/backend/app/routers/dashboard.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends, Query from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, func, or_ +from sqlalchemy import select, func, or_, case from datetime import datetime, date, timedelta from typing import Optional, List, Dict, Any @@ -84,20 +84,16 @@ async def get_dashboard( projects_by_status_result = await db.execute(projects_by_status_query) projects_by_status = {row[0]: row[1] for row in projects_by_status_result} - # Total incomplete todos count (scoped to user) - total_incomplete_result = await db.execute( - select(func.count(Todo.id)).where( - Todo.user_id == current_user.id, - Todo.completed == False, - ) + # Todo counts: total and incomplete in a single query + todo_counts_result = await db.execute( + select( + func.count(Todo.id).label("total"), + func.count(case((Todo.completed == False, Todo.id))).label("incomplete"), + ).where(Todo.user_id == current_user.id) ) - total_incomplete_todos = total_incomplete_result.scalar() - - # Total todos count (for progress ring ratio) - total_todos_result = await db.execute( - select(func.count(Todo.id)).where(Todo.user_id == current_user.id) - ) - total_todos = total_todos_result.scalar() + todo_row = todo_counts_result.one() + total_todos = todo_row.total + total_incomplete_todos = todo_row.incomplete # Starred events (upcoming, ordered by date, scoped to user's calendars) starred_query = select(CalendarEvent).where( diff --git a/frontend/src/components/dashboard/CalendarWidget.tsx b/frontend/src/components/dashboard/CalendarWidget.tsx index 8703527..05732b6 100644 --- a/frontend/src/components/dashboard/CalendarWidget.tsx +++ b/frontend/src/components/dashboard/CalendarWidget.tsx @@ -33,6 +33,7 @@ function getProgressPercent(event: DashboardEvent, now: Date): number { if (event.all_day) return 0; const start = new Date(event.start_datetime).getTime(); const end = new Date(event.end_datetime).getTime(); + if (end <= start) return 0; const current = now.getTime(); if (current >= end) return 100; if (current <= start) return 0; diff --git a/frontend/src/components/dashboard/DashboardPage.tsx b/frontend/src/components/dashboard/DashboardPage.tsx index 46692c9..f1b3855 100644 --- a/frontend/src/components/dashboard/DashboardPage.tsx +++ b/frontend/src/components/dashboard/DashboardPage.tsx @@ -272,7 +272,7 @@ export default function DashboardPage() {
{/* Left: Upcoming feed (wider) */}
- +
{/* Right: Countdown + Today's events + todos stacked */} diff --git a/frontend/src/components/dashboard/UpcomingWidget.tsx b/frontend/src/components/dashboard/UpcomingWidget.tsx index a8d9a60..069031d 100644 --- a/frontend/src/components/dashboard/UpcomingWidget.tsx +++ b/frontend/src/components/dashboard/UpcomingWidget.tsx @@ -17,13 +17,11 @@ import type { UpcomingItem } from '@/types'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { ScrollArea } from '@/components/ui/scroll-area'; import { cn } from '@/lib/utils'; -import { getRelativeTime } from '@/lib/date-utils'; -import { toLocalDatetime } from '@/lib/date-utils'; +import { getRelativeTime, toLocalDatetime } from '@/lib/date-utils'; import api from '@/lib/api'; interface UpcomingWidgetProps { items: UpcomingItem[]; - days?: number; } const typeConfig: Record = { @@ -216,6 +214,7 @@ export default function UpcomingWidget({ items }: UpcomingWidgetProps) { focusMode ? 'bg-accent/15 text-accent' : 'text-muted-foreground hover:text-foreground hover:bg-white/5' )} title={focusMode ? 'Show all days' : 'Focus: Today + Tomorrow'} + aria-label={focusMode ? 'Show all days' : 'Focus: Today + Tomorrow'} > @@ -226,6 +225,7 @@ export default function UpcomingWidget({ items }: UpcomingWidgetProps) { showPast ? 'bg-accent/15 text-accent' : 'text-muted-foreground hover:text-foreground hover:bg-white/5' )} title={showPast ? 'Hide past events' : 'Show past events'} + aria-label={showPast ? 'Hide past events' : 'Show past events'} > {showPast ? : }