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:
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user