Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Monday.com Clone</title> | |
| <script src="https://cdn.jsdelivr.net/npm/react@18.0.0/umd/react.development.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/react-dom@18.0.0/umd/react-dom.development.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.js"></script> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| } | |
| .board-column { | |
| min-width: 280px; | |
| } | |
| .task-card { | |
| transition: all 0.2s ease; | |
| } | |
| .task-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
| } | |
| .dragging { | |
| opacity: 0.5; | |
| } | |
| .drag-over { | |
| background-color: rgba(99, 102, 241, 0.1); | |
| } | |
| .status-pending { background-color: #fef3c7; } | |
| .status-in-progress { background-color: #dbeafe; } | |
| .status-completed { background-color: #d1fae5; } | |
| .status-blocked { background-color: #fecaca; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script type="text/babel"> | |
| const { useState, useEffect } = React; | |
| const App = () => { | |
| const [boards, setBoards] = useState([ | |
| { id: 1, name: 'Marketing Campaign', active: true }, | |
| { id: 2, name: 'Product Development', active: false }, | |
| { id: 3, name: 'Customer Support', active: false }, | |
| { id: 4, name: 'Sales Pipeline', active: false } | |
| ]); | |
| const [columns, setColumns] = useState([ | |
| { id: 1, title: 'To Do', tasks: [ | |
| { id: 1, title: 'Create social media content', assignee: 'Sarah', status: 'pending', priority: 'high' }, | |
| { id: 2, title: 'Design new landing page', assignee: 'Mike', status: 'pending', priority: 'medium' } | |
| ]}, | |
| { id: 2, title: 'In Progress', tasks: [ | |
| { id: 3, title: 'Review analytics dashboard', assignee: 'John', status: 'in-progress', priority: 'high' }, | |
| { id: 4, title: 'Update SEO keywords', assignee: 'Emma', status: 'in-progress', priority: 'medium' } | |
| ]}, | |
| { id: 3, title: 'Review', tasks: [ | |
| { id: 5, title: 'QA testing for v2.0', assignee: 'David', status: 'pending', priority: 'high' } | |
| ]}, | |
| { id: 4, title: 'Done', tasks: [ | |
| { id: 6, title: 'Fix login bug', assignee: 'Lisa', status: 'completed', priority: 'low' } | |
| ]} | |
| ]); | |
| const [showAddTaskModal, setShowAddTaskModal] = useState(false); | |
| const [newTask, setNewTask] = useState({ title: '', assignee: '', status: 'pending', priority: 'medium' }); | |
| const [draggedTask, setDraggedTask] = useState(null); | |
| const [draggedFromColumn, setDraggedFromColumn] = useState(null); | |
| const handleAddTask = () => { | |
| if (newTask.title.trim()) { | |
| const task = { | |
| id: Date.now(), | |
| title: newTask.title, | |
| assignee: newTask.assignee, | |
| status: newTask.status, | |
| priority: newTask.priority | |
| }; | |
| setColumns(prev => prev.map(col => { | |
| if (col.id === parseInt(newTask.status)) { | |
| return { ...col, tasks: [...col.tasks, task] }; | |
| } | |
| return col; | |
| })); | |
| setNewTask({ title: '', assignee: '', status: 'pending', priority: 'medium' }); | |
| setShowAddTaskModal(false); | |
| } | |
| }; | |
| const handleDragStart = (taskId, columnId) => { | |
| setDraggedTask(taskId); | |
| setDraggedFromColumn(columnId); | |
| }; | |
| const handleDragOver = (e) => { | |
| e.preventDefault(); | |
| }; | |
| const handleDrop = (columnId) => { | |
| if (draggedTask && draggedFromColumn !== columnId) { | |
| const task = columns.find(col => col.id === draggedFromColumn) | |
| .tasks.find(t => t.id === draggedTask); | |
| setColumns(prev => prev.map(col => { | |
| if (col.id === draggedFromColumn) { | |
| return { ...col, tasks: col.tasks.filter(t => t.id !== draggedTask) }; | |
| } | |
| if (col.id === columnId) { | |
| return { ...col, tasks: [...col.tasks, { ...task, status: col.id.toString() }] }; | |
| } | |
| return col; | |
| })); | |
| } | |
| setDraggedTask(null); | |
| setDraggedFromColumn(null); | |
| }; | |
| const getStatusColor = (status) => { | |
| switch(status) { | |
| case 'pending': return 'status-pending'; | |
| case 'in-progress': return 'status-in-progress'; | |
| case 'completed': return 'status-completed'; | |
| case 'blocked': return 'status-blocked'; | |
| default: return 'status-pending'; | |
| } | |
| }; | |
| const getPriorityColor = (priority) => { | |
| switch(priority) { | |
| case 'high': return 'text-red-600'; | |
| case 'medium': return 'text-yellow-600'; | |
| case 'low': return 'text-green-600'; | |
| default: return 'text-gray-600'; | |
| } | |
| }; | |
| return ( | |
| <div className="h-screen flex flex-col bg-gray-50"> | |
| {/* Header */} | |
| <header className="bg-white border-b border-gray-200 px-6 py-3 flex items-center justify-between"> | |
| <div className="flex items-center space-x-4"> | |
| <div className="flex items-center space-x-2"> | |
| <div className="w-8 h-8 bg-indigo-600 rounded flex items-center justify-center"> | |
| <i className="fas fa-th text-white text-sm"></i> | |
| </div> | |
| <span className="text-xl font-bold text-gray-900">Monday</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" className="text-sm text-indigo-600 hover:text-indigo-800">Built with anycoder</a> | |
| </div> | |
| <div className="flex items-center space-x-4"> | |
| <button className="p-2 text-gray-500 hover:text-gray-700"> | |
| <i className="fas fa-search"></i> | |
| </button> | |
| <button className="p-2 text-gray-500 hover:text-gray-700"> | |
| <i className="fas fa-bell"></i> | |
| </button> | |
| <div className="w-8 h-8 bg-indigo-600 rounded-full flex items-center justify-center"> | |
| <span className="text-white text-sm font-medium">JD</span> | |
| </div> | |
| </div> | |
| </header> | |
| <div className="flex flex-1 overflow-hidden"> | |
| {/* Sidebar */} | |
| <aside className="w-64 bg-white border-r border-gray-200 p-4"> | |
| <div className="mb-6"> | |
| <h2 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">WORKSPACES</h2> | |
| <div className="space-y-1"> | |
| <div className="flex items-center space-x-3 p-2 rounded-lg bg-indigo-50 text-indigo-700"> | |
| <i className="fas fa-building text-sm"></i> | |
| <span className="text-sm font-medium">Marketing Team</span> | |
| </div> | |
| <div className="flex items-center space-x-3 p-2 rounded-lg text-gray-600 hover:bg-gray-50"> | |
| <i className="fas fa-building text-sm"></i> | |
| <span className="text-sm">Development</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div> | |
| <div className="flex items-center justify-between mb-3"> | |
| <h2 className="text-xs font-semibold text-gray-500 uppercase tracking-wider">BOARDS</h2> | |
| <button className="text-gray-400 hover:text-gray-600"> | |
| <i className="fas fa-plus text-xs"></i> | |
| </button> | |
| </div> | |
| <div className="space-y-1"> | |
| {boards.map(board => ( | |
| <div | |
| key={board.id} | |
| className={`flex items-center space-x-3 p-2 rounded-lg cursor-pointer ${ | |
| board.active ? 'bg-indigo-50 text-indigo-700' : 'text-gray-600 hover:bg-gray-50' | |
| }`} | |
| onClick={() => setBoards(boards.map(b => ({...b, active: b.id === board.id})))} | |
| > | |
| <i className="fas fa-th-large text-sm"></i> | |
| <span className="text-sm">{board.name}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </aside> | |
| {/* Main Content */} | |
| <main className="flex-1 overflow-auto p-6"> | |
| <div className="mb-6"> | |
| <div className="flex items-center justify-between mb-4"> | |
| <h1 className="text-2xl font-bold text-gray-900">Marketing Campaign</h1> | |
| <div className="flex items-center space-x-2"> | |
| <button className="px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 flex items-center space-x-1"> | |
| <i className="fas fa-plus text-xs"></i> | |
| <span>Add Column</span> | |
| </button> | |
| <button | |
| onClick={() => setShowAddTaskModal(true)} | |
| className="px-4 py-2 bg-indigo-600 text-white text-sm rounded-lg hover:bg-indigo-700 flex items-center space-x-2" | |
| > | |
| <i className="fas fa-plus text-xs"></i> | |
| <span>Add Task</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div className="flex items-center space-x-4 text-sm text-gray-500"> | |
| <span>Today</span> | |
| <span>•</span> | |
| <span>4 tasks</span> | |
| <span>•</span> | |
| <span>2 team members</span> | |
| </div> | |
| </div> | |
| {/* Board */} | |
| <div className="flex space-x-4 overflow-x-auto pb-4"> | |
| {columns.map(column => ( | |
| <div | |
| key={column.id} | |
| className="board-column flex-shrink-0" | |
| onDragOver={handleDragOver} | |
| onDrop={() => handleDrop(column.id)} | |
| > | |
| <div className="mb-3"> | |
| <div className="flex items-center justify-between mb-2"> | |
| <h3 className="font-semibold text-gray-900">{column.title}</h3> | |
| <span className="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded-full"> | |
| {column.tasks.length} | |
| </span> | |
| </div> | |
| <div className="space-y-2"> | |
| {column.tasks.map(task => ( | |
| <div | |
| key={task.id} | |
| draggable | |
| onDragStart={() => handleDragStart(task.id, column.id)} | |
| className={`task-card p-3 bg-white rounded-lg border border-gray-200 cursor-move ${getStatusColor(task.status)}`} | |
| > | |
| <div className="flex items-start justify-between mb-2"> | |
| <h4 className="font-medium text-sm text-gray-900 flex-1">{task.title}</h4> | |
| <button className="text-gray-400 hover:text-gray-600 ml-2"> | |
| <i className="fas fa-ellipsis-h text-xs"></i> | |
| </button> | |
| </div> | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center space-x-2"> | |
| <div className="w-6 h-6 bg-gray-300 rounded-full flex items-center justify-center"> | |
| <span className="text-xs text-gray-600">{task.assignee[0]}</span> | |
| </div> | |
| <span className="text-xs text-gray-600">{task.assignee}</span> | |
| </div> | |
| <span className={`text-xs font-medium ${getPriorityColor(task.priority)}`}> | |
| {task.priority} | |
| </span> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| <button | |
| onClick={() => { | |
| setNewTask({...newTask, status: column.id.toString()}); | |
| setShowAddTaskModal(true); | |
| }} | |
| className="w-full mt-2 p-2 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-gray-400 hover:text-gray-600 text-sm flex items-center justify-center space-x-1" | |
| > | |
| <i className="fas fa-plus text-xs"></i> | |
| <span>Add Task</span> | |
| </button> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </main> | |
| </div> | |
| {/* Add Task Modal */} | |
| {showAddTaskModal && ( | |
| <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"> | |
| <div className="bg-white rounded-lg p-6 w-full max-w-md"> | |
| <h2 className="text-lg font-semibold mb-4">Add New Task</h2> | |
| <div className="space-y-4"> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-1">Task Name</label> | |
| <input | |
| type="text" | |
| value={newTask.title} | |
| onChange={(e) => setNewTask({...newTask, title: e.target.value})} | |
| className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" | |
| placeholder="Enter task name" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-1">Assignee</label> | |
| <input | |
| type="text" | |
| value={newTask.assignee} | |
| onChange={(e) => setNewTask({...newTask, assignee: e.target.value})} | |
| className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" | |
| placeholder="Enter assignee name" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-1">Status</label> | |
| <select | |
| value={newTask.status} | |
| onChange={(e) => setNewTask({...newTask, status: e.target.value})} | |
| className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" | |
| > | |
| <option value="1">To Do</option> | |
| <option value="2">In Progress</option> | |
| <option value="3">Review</option> | |
| <option value="4">Done</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label className="block text-sm font-medium text-gray-700 mb-1">Priority</label> | |
| <select | |
| value={newTask.priority} | |
| onChange={(e) => setNewTask({...newTask, priority: e.target.value})} | |
| className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" | |
| > | |
| <option value="low">Low</option> | |
| <option value="medium">Medium</option> | |
| <option value="high">High</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div className="flex justify-end space-x-3 mt-6"> | |
| <button | |
| onClick={() => setShowAddTaskModal(false)} | |
| className="px-4 py-2 text-gray-600 hover:text-gray-800" | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| onClick={handleAddTask} | |
| className="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700" | |
| > | |
| Add Task | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| ReactDOM.render(<App />, document.getElementById('root')); | |
| </script> | |
| </body> | |
| </html> |