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:
parent
b41b0b6635
commit
ac3f746ba3
@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, Depends, Query
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
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 datetime import datetime, date, timedelta
|
||||||
from typing import Optional, List, Dict, Any
|
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_result = await db.execute(projects_by_status_query)
|
||||||
projects_by_status = {row[0]: row[1] for row in projects_by_status_result}
|
projects_by_status = {row[0]: row[1] for row in projects_by_status_result}
|
||||||
|
|
||||||
# Total incomplete todos count (scoped to user)
|
# Todo counts: total and incomplete in a single query
|
||||||
total_incomplete_result = await db.execute(
|
todo_counts_result = await db.execute(
|
||||||
select(func.count(Todo.id)).where(
|
select(
|
||||||
Todo.user_id == current_user.id,
|
func.count(Todo.id).label("total"),
|
||||||
Todo.completed == False,
|
func.count(case((Todo.completed == False, Todo.id))).label("incomplete"),
|
||||||
)
|
).where(Todo.user_id == current_user.id)
|
||||||
)
|
)
|
||||||
total_incomplete_todos = total_incomplete_result.scalar()
|
todo_row = todo_counts_result.one()
|
||||||
|
total_todos = todo_row.total
|
||||||
# Total todos count (for progress ring ratio)
|
total_incomplete_todos = todo_row.incomplete
|
||||||
total_todos_result = await db.execute(
|
|
||||||
select(func.count(Todo.id)).where(Todo.user_id == current_user.id)
|
|
||||||
)
|
|
||||||
total_todos = total_todos_result.scalar()
|
|
||||||
|
|
||||||
# Starred events (upcoming, ordered by date, scoped to user's calendars)
|
# Starred events (upcoming, ordered by date, scoped to user's calendars)
|
||||||
starred_query = select(CalendarEvent).where(
|
starred_query = select(CalendarEvent).where(
|
||||||
|
|||||||
@ -33,6 +33,7 @@ function getProgressPercent(event: DashboardEvent, now: Date): number {
|
|||||||
if (event.all_day) return 0;
|
if (event.all_day) return 0;
|
||||||
const start = new Date(event.start_datetime).getTime();
|
const start = new Date(event.start_datetime).getTime();
|
||||||
const end = new Date(event.end_datetime).getTime();
|
const end = new Date(event.end_datetime).getTime();
|
||||||
|
if (end <= start) return 0;
|
||||||
const current = now.getTime();
|
const current = now.getTime();
|
||||||
if (current >= end) return 100;
|
if (current >= end) return 100;
|
||||||
if (current <= start) return 0;
|
if (current <= start) return 0;
|
||||||
|
|||||||
@ -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' }}>
|
<div className="grid gap-3 sm:gap-5 lg:grid-cols-5 animate-slide-up" style={{ animationDelay: '100ms', animationFillMode: 'backwards' }}>
|
||||||
{/* Left: Upcoming feed (wider) */}
|
{/* Left: Upcoming feed (wider) */}
|
||||||
<div className="lg:col-span-3 flex flex-col">
|
<div className="lg:col-span-3 flex flex-col">
|
||||||
<UpcomingWidget items={upcomingData?.items ?? []} days={upcomingData?.days} />
|
<UpcomingWidget items={upcomingData?.items ?? []} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Countdown + Today's events + todos stacked */}
|
{/* Right: Countdown + Today's events + todos stacked */}
|
||||||
|
|||||||
@ -17,13 +17,11 @@ import type { UpcomingItem } from '@/types';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { getRelativeTime } from '@/lib/date-utils';
|
import { getRelativeTime, toLocalDatetime } from '@/lib/date-utils';
|
||||||
import { toLocalDatetime } from '@/lib/date-utils';
|
|
||||||
import api from '@/lib/api';
|
import api from '@/lib/api';
|
||||||
|
|
||||||
interface UpcomingWidgetProps {
|
interface UpcomingWidgetProps {
|
||||||
items: UpcomingItem[];
|
items: UpcomingItem[];
|
||||||
days?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const typeConfig: Record<string, { hoverGlow: string; pillBg: string; pillText: string; label: string }> = {
|
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'
|
focusMode ? 'bg-accent/15 text-accent' : 'text-muted-foreground hover:text-foreground hover:bg-white/5'
|
||||||
)}
|
)}
|
||||||
title={focusMode ? 'Show all days' : 'Focus: Today + Tomorrow'}
|
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" />
|
<Target className="h-3.5 w-3.5" />
|
||||||
</button>
|
</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'
|
showPast ? 'bg-accent/15 text-accent' : 'text-muted-foreground hover:text-foreground hover:bg-white/5'
|
||||||
)}
|
)}
|
||||||
title={showPast ? 'Hide past events' : 'Show past events'}
|
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" />}
|
{showPast ? <Eye className="h-3.5 w-3.5" /> : <EyeOff className="h-3.5 w-3.5" />}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user