Fix 5 testing bugs: priority badge width, recurring event errors, nested subtask UI, comment timestamps, close button

- Widen priority badge from w-10 to w-14 to fit "medium" text, add "none" case
- Guard against null end_datetime in event update validation
- Exclude current event from this_and_future DELETE to prevent 404
- Use Python-side datetime.now for comment timestamps (avoids UTC offset)
- Hide "Add subtask" button when viewing a subtask (prevents nested nesting)
- Add X close button to TaskDetailPanel header on desktop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-02-22 11:53:09 +08:00
parent 6e50089201
commit bfe97fd749
5 changed files with 33 additions and 14 deletions

View File

@ -1,4 +1,4 @@
from sqlalchemy import Text, Integer, ForeignKey, func
from sqlalchemy import Text, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship as sa_relationship
from datetime import datetime
from app.database import Base
@ -12,7 +12,7 @@ class TaskComment(Base):
Integer, ForeignKey("project_tasks.id", ondelete="CASCADE"), nullable=False, index=True
)
content: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
# Relationships
task: Mapped["ProjectTask"] = sa_relationship(back_populates="comments")

View File

@ -287,7 +287,7 @@ async def update_event(
start = update_data.get("start_datetime", event.start_datetime)
end_dt = update_data.get("end_datetime", event.end_datetime)
if end_dt < start:
if end_dt is not None and end_dt < start:
raise HTTPException(status_code=400, detail="End datetime must be after start datetime")
if scope == "this":
@ -316,6 +316,7 @@ async def update_event(
delete(CalendarEvent).where(
CalendarEvent.parent_event_id == parent_id,
CalendarEvent.original_start >= this_original_start,
CalendarEvent.id != event_id,
)
)

View File

@ -57,10 +57,11 @@ export default function UpcomingWidget({ items, days = 7 }: UpcomingWidgetProps)
{config.label}
</span>
<span className={cn(
'text-[9px] font-semibold px-1.5 py-0.5 rounded shrink-0 w-10 text-center',
'text-[9px] font-semibold px-1.5 py-0.5 rounded shrink-0 w-14 text-center',
item.priority === 'high' ? 'bg-red-500/10 text-red-400' :
item.priority === 'medium' ? 'bg-yellow-500/10 text-yellow-400' :
item.priority === 'low' ? 'bg-green-500/10 text-green-400' :
item.priority === 'none' ? 'bg-gray-500/10 text-gray-400' :
'invisible'
)}>
{item.priority || ''}

View File

@ -575,6 +575,7 @@ export default function ProjectDetail() {
onEdit={(task) => openTaskForm(task, null)}
onDelete={handleDeleteTask}
onAddSubtask={(parentId) => openTaskForm(null, parentId)}
onClose={() => setSelectedTaskId(null)}
/>
</div>
</div>
@ -603,6 +604,7 @@ export default function ProjectDetail() {
onEdit={(task) => openTaskForm(task, null)}
onDelete={handleDeleteTask}
onAddSubtask={(parentId) => openTaskForm(null, parentId)}
onClose={() => setSelectedTaskId(null)}
/>
</div>
</div>

View File

@ -4,7 +4,7 @@ import { toast } from 'sonner';
import { format, formatDistanceToNow, parseISO } from 'date-fns';
import {
Pencil, Trash2, Plus, MessageSquare, ClipboardList,
Calendar, User, Flag, Activity, Send,
Calendar, User, Flag, Activity, Send, X,
} from 'lucide-react';
import api, { getErrorMessage } from '@/lib/api';
import type { ProjectTask, TaskComment, Person } from '@/types';
@ -37,6 +37,7 @@ interface TaskDetailPanelProps {
onEdit: (task: ProjectTask) => void;
onDelete: (taskId: number) => void;
onAddSubtask: (parentId: number) => void;
onClose?: () => void;
}
export default function TaskDetailPanel({
@ -45,6 +46,7 @@ export default function TaskDetailPanel({
onEdit,
onDelete,
onAddSubtask,
onClose,
}: TaskDetailPanelProps) {
const queryClient = useQueryClient();
const [commentText, setCommentText] = useState('');
@ -145,6 +147,17 @@ export default function TaskDetailPanel({
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
{onClose && (
<Button
variant="ghost"
size="icon"
className="h-7 w-7"
onClick={onClose}
title="Close panel"
>
<X className="h-3.5 w-3.5" />
</Button>
)}
</div>
</div>
</div>
@ -222,6 +235,7 @@ export default function TaskDetailPanel({
</span>
)}
</h4>
{!task.parent_task_id && (
<Button
variant="ghost"
size="sm"
@ -231,6 +245,7 @@ export default function TaskDetailPanel({
<Plus className="h-3 w-3 mr-1" />
Add
</Button>
)}
</div>
{task.subtasks.length > 0 ? (
<div className="space-y-1">