Improve task calendar usability and task creation functionality

Add `onKeyDown` handler to the task title input in `TaskCreationModal.tsx` to allow task creation with the Enter key. Enhance `TasksWithCalendar.tsx` to include `hoveredDate` state and `handleDragEnter`/`handleDragLeave` handlers for visual feedback during drag-and-drop operations on the calendar. Adjust the calendar's positioning to be fixed at the bottom with full width and modify styling for task cards during drag operations. Fix the display of the "today" border on the calendar.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/sqJIbnU
This commit is contained in:
paul-nothaft
2025-09-11 10:02:12 +00:00
parent b6d187d477
commit 9b31cc5083
4 changed files with 165 additions and 52 deletions
@@ -46,6 +46,13 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
onClose();
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSave();
}
};
const handleClose = () => {
setTitle('');
setDescription('');
@@ -70,6 +77,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
placeholder="What needs to be done?"
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={handleKeyDown}
className="text-base"
data-testid="input-task-title"
autoFocus
+66 -52
View File
@@ -25,6 +25,7 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
const [filterBy, setFilterBy] = useState<FilterOption>('all');
const [calendarStartDate, setCalendarStartDate] = useState(startOfToday());
const [draggedTask, setDraggedTask] = useState<string | null>(null);
const [hoveredDate, setHoveredDate] = useState<Date | null>(null);
const [completionModal, setCompletionModal] = useState<{ isOpen: boolean; task: Task | null }>({
isOpen: false,
task: null
@@ -124,6 +125,16 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
e.dataTransfer.dropEffect = 'move';
};
const handleDragEnter = (date: Date) => {
if (draggedTask) {
setHoveredDate(date);
}
};
const handleDragLeave = () => {
setHoveredDate(null);
};
const navigateCalendar = (direction: 'prev' | 'next') => {
const newDate = addDays(calendarStartDate, direction === 'next' ? 7 : -7);
setCalendarStartDate(newDate);
@@ -235,8 +246,8 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
draggable
onDragStart={(e) => handleDragStart(task.id, e)}
onDragEnd={handleDragEnd}
className={`cursor-move transition-transform ${
draggedTask === task.id ? 'opacity-50 scale-95' : 'hover:scale-[1.02]'
className={`cursor-move transition-transform hover-elevate ${
draggedTask === task.id ? 'opacity-50 scale-95' : ''
}`}
>
<TaskCard
@@ -269,59 +280,62 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
)}
</div>
{/* Calendar Section */}
<div className="space-y-3 border-t pt-4">
{/* Calendar Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Calendar className="w-5 h-5 text-primary" />
<h3 className="text-base font-semibold" data-testid="text-calendar-title">
Next 7 Days
</h3>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={() => navigateCalendar('prev')}
data-testid="button-prev-week"
className="w-8 h-8"
>
<ChevronLeft className="w-4 h-4" />
</Button>
{/* Calendar Section - Fixed at bottom with full width */}
<div className="fixed bottom-16 left-0 right-0 bg-background border-t shadow-lg z-10">
<div className="p-4 max-h-[35vh] overflow-y-auto">
{/* Calendar Header */}
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-primary" />
<h3 className="text-sm font-semibold" data-testid="text-calendar-title">
Next 7 Days
</h3>
</div>
<Button
variant="outline"
size="icon"
onClick={() => navigateCalendar('next')}
data-testid="button-next-week"
className="w-8 h-8"
>
<ChevronRight className="w-4 h-4" />
</Button>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="icon"
onClick={() => navigateCalendar('prev')}
data-testid="button-prev-week"
>
<ChevronLeft className="w-3 h-3" />
</Button>
<Button
variant="outline"
size="icon"
onClick={() => navigateCalendar('next')}
data-testid="button-next-week"
>
<ChevronRight className="w-3 h-3" />
</Button>
</div>
</div>
</div>
{/* Horizontal Scrollable Calendar */}
<div className="overflow-x-auto pb-2">
<div className="flex gap-3 min-w-max">
{/* Full Width Calendar Grid - Responsive */}
<div className="grid grid-cols-4 sm:grid-cols-7 gap-2">
{dates.map((date, index) => {
const dayTasks = getTasksForDate(date);
const isToday = isSameDay(date, new Date());
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
const isHovered = hoveredDate && isSameDay(date, hoveredDate);
return (
<Card
key={index}
className={`flex-shrink-0 w-32 p-3 min-h-[120px] transition-all ${
isToday ? 'ring-2 ring-primary' : ''
className={`p-2 min-h-[100px] transition-all border-2 ${
isToday ? 'border-primary ring-1 ring-primary ring-offset-1 ring-offset-background' : 'border-border'
} ${
isWeekend ? 'bg-muted/30' : ''
} ${
draggedTask ? 'hover:ring-2 hover:ring-primary/50 hover:bg-primary/5' : 'hover-elevate'
isHovered && draggedTask ? 'ring-2 ring-primary bg-primary/10 border-primary' : ''
} ${
draggedTask && !isHovered ? 'border-dashed border-primary/30' : ''
}`}
onDragOver={handleDragOver}
onDragEnter={() => handleDragEnter(date)}
onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(date, e)}
onClick={() => {
console.log('Date selected:', date.toLocaleDateString());
@@ -332,7 +346,7 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
<div className="text-xs text-muted-foreground font-medium">
{format(date, 'EEE')}
</div>
<div className={`text-sm font-semibold ${
<div className={`text-xs font-semibold ${
isToday ? 'text-primary' : ''
}`}>
{format(date, 'MMM d')}
@@ -353,7 +367,7 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
variant="outline"
className="w-full justify-start text-xs p-1 h-auto text-left"
>
<div className="truncate">
<div className="truncate text-xs">
{task.title}
</div>
</Badge>
@@ -361,18 +375,20 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
))}
{dayTasks.length > 2 && (
<Badge variant="secondary" className="w-full justify-center text-xs">
<Badge variant="secondary" className="w-full justify-center text-xs py-0">
+{dayTasks.length - 2}
</Badge>
)}
{dayTasks.length === 0 && (
<div className={`text-center text-xs py-4 rounded border-2 border-dashed transition-colors ${
draggedTask
? 'border-primary/50 text-primary/70 bg-primary/5'
: 'border-muted text-muted-foreground'
<div className={`text-center text-xs py-2 rounded border border-dashed transition-all ${
isHovered && draggedTask
? 'border-primary text-primary bg-primary/5'
: draggedTask
? 'border-primary/50 text-primary/70'
: 'border-muted-foreground/30 text-muted-foreground'
}`}>
{draggedTask ? 'Drop here' : 'No tasks'}
{isHovered && draggedTask ? 'Drop here' : draggedTask ? 'Drop' : ''}
</div>
)}
</div>
@@ -381,13 +397,11 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit }: T
})}
</div>
</div>
{/* Calendar Instructions */}
<div className="text-xs text-muted-foreground text-center">
Drag tasks from above to schedule them Use arrow buttons to see more days
</div>
</div>
{/* Add bottom padding to prevent content overlap */}
<div className="h-[40vh]" />
{/* Time Completion Modal */}
<TimeCompletionModal
isOpen={completionModal.isOpen}