Download src/components/TaskCard.js from SyedFouzan/taskflow-titan-workflow-wizardry: direct link, hf CLI and curl.
- Browser
- Download file 5.83 kB
-
https://huggingface.co/spaces/SyedFouzan/taskflow-titan-workflow-wizardry/resolve/main/src/components/TaskCard.js
- Command line
-
hf download hf://spaces/SyedFouzan/taskflow-titan-workflow-wizardry/src/components/TaskCard.js
-
curl -L -o TaskCard.js https://huggingface.co/spaces/SyedFouzan/taskflow-titan-workflow-wizardry/resolve/main/src/components/TaskCard.js
5.83 kB
| import React, { useState } from 'react'; | |
| import { motion } from 'framer-motion'; | |
| import { | |
| Calendar, | |
| User, | |
| MessageSquare, | |
| Paperclip, | |
| MoreVertical, | |
| CheckCircle, | |
| Clock, | |
| AlertCircle | |
| } from 'lucide-react'; | |
| import { tasksAPI } from '../services/api'; | |
| import toast from 'react-hot-toast'; | |
| const TaskCard = ({ task, onUpdate, onEdit, onDelete }) => { | |
| const [isMenuOpen, setIsMenuOpen] = useState(false); | |
| const [isUpdating, setIsUpdating] = useState(false); | |
| const getStatusIcon = (status) => { | |
| switch (status) { | |
| case 'completed': | |
| return <CheckCircle className="w-4 h-4 text-green-500" />; | |
| case 'in-progress': | |
| return <Clock className="w-4 h-4 text-blue-500" />; | |
| case 'pending': | |
| return <AlertCircle className="w-4 h-4 text-yellow-500" />; | |
| default: | |
| return null; | |
| } | |
| }; | |
| const getPriorityColor = (priority) => { | |
| switch (priority) { | |
| case 'high': | |
| return 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400'; | |
| case 'medium': | |
| return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400'; | |
| case 'low': | |
| return 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'; | |
| default: | |
| return 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400'; | |
| } | |
| }; | |
| const handleStatusChange = async (newStatus) => { | |
| setIsUpdating(true); | |
| try { | |
| await tasksAPI.updateStatus(task._id, newStatus); | |
| onUpdate && onUpdate(task._id, { status: newStatus }); | |
| toast.success('Task status updated'); | |
| } catch (error) { | |
| toast.error('Failed to update status'); | |
| } finally { | |
| setIsUpdating(false); | |
| } | |
| }; | |
| const formatDate = (date) => { | |
| return new Date(date).toLocaleDateString('en-US', { | |
| month: 'short', | |
| day: 'numeric', | |
| year: 'numeric' | |
| }); | |
| }; | |
| return ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="card hover:shadow-md transition-shadow duration-200" | |
| > | |
| <div className="flex items-start justify-between mb-4"> | |
| <div className="flex-1"> | |
| <div className="flex items-center gap-2 mb-2"> | |
| {getStatusIcon(task.status)} | |
| <h3 className="font-semibold text-gray-900 dark:text-gray-100"> | |
| {task.title} | |
| </h3> | |
| </div> | |
| <span className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${getPriorityColor(task.priority)}`}> | |
| {task.priority} | |
| </span> | |
| </div> | |
| <div className="relative"> | |
| <button | |
| onClick={() => setIsMenuOpen(!isMenuOpen)} | |
| className="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" | |
| > | |
| <MoreVertical className="w-4 h-4 text-gray-500" /> | |
| </button> | |
| {isMenuOpen && ( | |
| <div className="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-10"> | |
| <button | |
| onClick={() => { | |
| onEdit && onEdit(task); | |
| setIsMenuOpen(false); | |
| }} | |
| className="flex items-center gap-2 w-full px-3 py-2 text-sm text-left hover:bg-gray-50 dark:hover:bg-gray-700" | |
| > | |
| Edit | |
| </button> | |
| <select | |
| value={task.status} | |
| onChange={(e) => { | |
| handleStatusChange(e.target.value); | |
| setIsMenuOpen(false); | |
| }} | |
| className="w-full px-3 py-2 text-sm bg-transparent border-0 focus:ring-0 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700" | |
| disabled={isUpdating} | |
| > | |
| <option value="pending">Pending</option> | |
| <option value="in-progress">In Progress</option> | |
| <option value="completed">Completed</option> | |
| </select> | |
| <button | |
| onClick={() => { | |
| onDelete && onDelete(task._id); | |
| setIsMenuOpen(false); | |
| }} | |
| className="flex items-center gap-2 w-full px-3 py-2 text-sm text-left text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20" | |
| > | |
| Delete | |
| </button> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| <p className="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2"> | |
| {task.description} | |
| </p> | |
| <div className="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400"> | |
| <div className="flex items-center gap-4"> | |
| {task.assignee && ( | |
| <div className="flex items-center gap-1"> | |
| <User className="w-3 h-3" /> | |
| <span>{task.assignee.name}</span> | |
| </div> | |
| )} | |
| {task.deadline && ( | |
| <div className="flex items-center gap-1"> | |
| <Calendar className="w-3 h-3" /> | |
| <span>{formatDate(task.deadline)}</span> | |
| </div> | |
| )} | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| {task.comments && task.comments.length > 0 && ( | |
| <div className="flex items-center gap-1"> | |
| <MessageSquare className="w-3 h-3" /> | |
| <span>{task.comments.length}</span> | |
| </div> | |
| )} | |
| {task.attachments && task.attachments.length > 0 && ( | |
| <div className="flex items-center gap-1"> | |
| <Paperclip className="w-3 h-3" /> | |
| <span>{task.attachments.length}</span> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </motion.div> | |
| ); | |
| }; | |
| export default TaskCard; |