Add core components for task management and navigation
This commit introduces the foundational UI components and logic for the task management application, including task creation, calendar views, Kanban boards, and navigation elements. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/yy9YLEW
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Clock, Calendar, Play, Pause, MoreHorizontal } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
status: 'todo' | 'inProgress' | 'done';
|
||||
priority: 'low' | 'medium' | 'high';
|
||||
dueDate?: Date;
|
||||
timeTracked: number; // in minutes
|
||||
isTracking: boolean;
|
||||
projectId?: string;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
interface TaskCardProps {
|
||||
task: Task;
|
||||
onPlay?: () => void;
|
||||
onPause?: () => void;
|
||||
onEdit?: () => void;
|
||||
onStatusChange?: (status: Task['status']) => void;
|
||||
isDragging?: boolean;
|
||||
}
|
||||
|
||||
export default function TaskCard({ task, onPlay, onPause, onEdit, onStatusChange, isDragging }: TaskCardProps) {
|
||||
const [isTimerRunning, setIsTimerRunning] = useState(task.isTracking);
|
||||
|
||||
const handleToggleTimer = () => {
|
||||
if (isTimerRunning) {
|
||||
onPause?.();
|
||||
console.log(`Timer paused for task: ${task.title}`);
|
||||
} else {
|
||||
onPlay?.();
|
||||
console.log(`Timer started for task: ${task.title}`);
|
||||
}
|
||||
setIsTimerRunning(!isTimerRunning);
|
||||
};
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'high': return 'bg-destructive text-destructive-foreground';
|
||||
case 'medium': return 'bg-yellow-500 text-white';
|
||||
default: return 'bg-muted text-muted-foreground';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'done': return 'bg-green-500 text-white';
|
||||
case 'inProgress': return 'bg-primary text-primary-foreground';
|
||||
default: return 'bg-muted text-muted-foreground';
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (minutes: number) => {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const mins = minutes % 60;
|
||||
return `${hours}h ${mins}m`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={`p-4 hover-elevate active-elevate-2 transition-all duration-200 ${
|
||||
isDragging ? 'rotate-1 scale-105 shadow-lg' : ''
|
||||
}`}
|
||||
data-testid={`card-task-${task.id}`}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-sm leading-tight truncate" data-testid={`text-task-title-${task.id}`}>
|
||||
{task.title}
|
||||
</h3>
|
||||
{task.description && (
|
||||
<p className="text-xs text-muted-foreground mt-1 line-clamp-2" data-testid={`text-task-description-${task.id}`}>
|
||||
{task.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 mt-3 flex-wrap">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={`text-xs ${getPriorityColor(task.priority)}`}
|
||||
data-testid={`badge-priority-${task.id}`}
|
||||
>
|
||||
{task.priority}
|
||||
</Badge>
|
||||
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={`text-xs ${getStatusColor(task.status)}`}
|
||||
data-testid={`badge-status-${task.id}`}
|
||||
>
|
||||
{task.status.replace(/([A-Z])/g, ' $1').toLowerCase()}
|
||||
</Badge>
|
||||
|
||||
{task.dueDate && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Calendar className="w-3 h-3" />
|
||||
<span data-testid={`text-due-date-${task.id}`}>
|
||||
{task.dueDate.toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{task.timeTracked > 0 && (
|
||||
<div className="flex items-center gap-1 mt-2 text-xs text-muted-foreground">
|
||||
<Clock className="w-3 h-3" />
|
||||
<span data-testid={`text-time-tracked-${task.id}`}>
|
||||
{formatTime(task.timeTracked)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit?.();
|
||||
console.log(`Edit task: ${task.title}`);
|
||||
}}
|
||||
data-testid={`button-edit-${task.id}`}
|
||||
className="w-6 h-6"
|
||||
>
|
||||
<MoreHorizontal className="w-3 h-3" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
variant={isTimerRunning ? "default" : "outline"}
|
||||
onClick={handleToggleTimer}
|
||||
data-testid={`button-timer-${task.id}`}
|
||||
className="w-6 h-6"
|
||||
>
|
||||
{isTimerRunning ? <Pause className="w-3 h-3" /> : <Play className="w-3 h-3" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user