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 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-12 00:16:00 +08:00
parent b41b0b6635
commit ac3f746ba3
4 changed files with 15 additions and 18 deletions

View File

@ -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(

View File

@ -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;

View File

@ -272,7 +272,7 @@ export default function DashboardPage() {
<div className="grid gap-3 sm:gap-5 lg:grid-cols-5 animate-slide-up" style={{ animationDelay: '100ms', animationFillMode: 'backwards' }}>
{/* Left: Upcoming feed (wider) */}
<div className="lg:col-span-3 flex flex-col">
<UpcomingWidget items={upcomingData?.items ?? []} days={upcomingData?.days} />
<UpcomingWidget items={upcomingData?.items ?? []} />
</div>
{/* Right: Countdown + Today's events + todos stacked */}

View File

@ -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<string, { hoverGlow: string; pillBg: string; pillText: string; label: string }> = {
@ -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'}
>
<Target className="h-3.5 w-3.5" />
</button>
@ -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 ? <Eye className="h-3.5 w-3.5" /> : <EyeOff className="h-3.5 w-3.5" />}
</button>