feat: MySQL integration, Docker setup, drag-and-drop kanban
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
import { useState } from 'react';
|
||||
import type { Task, User, Status, Priority } from './data';
|
||||
import { USERS, STATUS_LABELS, getUserById } from './data';
|
||||
import { STATUS_LABELS, getUserById } from './data';
|
||||
import { Avatar, Tag, ProgressBar } from './Shared';
|
||||
|
||||
interface DrawerProps {
|
||||
task: Task; currentUser: User; onClose: () => void;
|
||||
onUpdate: (updated: Task) => void;
|
||||
users: User[];
|
||||
}
|
||||
|
||||
export function TaskDrawer({ task, currentUser, onClose, onUpdate }: DrawerProps) {
|
||||
export function TaskDrawer({ task, currentUser, onClose, onUpdate, users }: DrawerProps) {
|
||||
const [commentText, setCommentText] = useState('');
|
||||
const [subtaskText, setSubtaskText] = useState('');
|
||||
|
||||
@@ -48,7 +49,7 @@ export function TaskDrawer({ task, currentUser, onClose, onUpdate }: DrawerProps
|
||||
setCommentText('');
|
||||
};
|
||||
|
||||
const reporter = getUserById(task.reporter);
|
||||
const reporter = getUserById(users, task.reporter);
|
||||
const doneCount = task.subtasks.filter(s => s.done).length;
|
||||
|
||||
return (
|
||||
@@ -67,15 +68,15 @@ export function TaskDrawer({ task, currentUser, onClose, onUpdate }: DrawerProps
|
||||
<div>
|
||||
<div className="drawer-meta-label">Assignee</div>
|
||||
<div className="drawer-meta-val">
|
||||
<Avatar userId={task.assignee} size={20} />
|
||||
<Avatar userId={task.assignee} size={20} users={users} />
|
||||
<select className="drawer-select" value={task.assignee} onChange={e => updateField('assignee', e.target.value)}>
|
||||
{USERS.map(u => <option key={u.id} value={u.id}>{u.name}</option>)}
|
||||
{users.map(u => <option key={u.id} value={u.id}>{u.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="drawer-meta-label">Reporter</div>
|
||||
<div className="drawer-meta-val"><Avatar userId={task.reporter} size={20} /> {reporter?.name}</div>
|
||||
<div className="drawer-meta-val"><Avatar userId={task.reporter} size={20} users={users} /> {reporter?.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="drawer-meta-label">Status</div>
|
||||
@@ -117,10 +118,10 @@ export function TaskDrawer({ task, currentUser, onClose, onUpdate }: DrawerProps
|
||||
<div className="drawer-section">
|
||||
<div className="drawer-section-title">Comments</div>
|
||||
{task.comments.map(c => {
|
||||
const cu = getUserById(c.userId);
|
||||
const cu = getUserById(users, c.userId);
|
||||
return (
|
||||
<div key={c.id} className="comment-item">
|
||||
<Avatar userId={c.userId} size={26} />
|
||||
<Avatar userId={c.userId} size={26} users={users} />
|
||||
<div className="comment-bubble">
|
||||
<div className="comment-header">
|
||||
<span className="comment-name">{cu?.name}</span>
|
||||
@@ -132,7 +133,7 @@ export function TaskDrawer({ task, currentUser, onClose, onUpdate }: DrawerProps
|
||||
);
|
||||
})}
|
||||
<div className="comment-input-row">
|
||||
<Avatar userId={currentUser.id} size={26} />
|
||||
<Avatar userId={currentUser.id} size={26} users={users} />
|
||||
<input placeholder="Add a comment..." value={commentText} onChange={e => setCommentText(e.target.value)} onKeyDown={e => e.key === 'Enter' && addComment()} />
|
||||
<button onClick={addComment}>Post</button>
|
||||
</div>
|
||||
@@ -157,12 +158,14 @@ interface ModalProps {
|
||||
onAdd: (task: Task) => void;
|
||||
defaultDate?: string;
|
||||
defaultStatus?: Status;
|
||||
users: User[];
|
||||
currentUser: User;
|
||||
}
|
||||
|
||||
export function AddTaskModal({ onClose, onAdd, defaultDate, defaultStatus }: ModalProps) {
|
||||
export function AddTaskModal({ onClose, onAdd, defaultDate, defaultStatus, users, currentUser }: ModalProps) {
|
||||
const [title, setTitle] = useState('');
|
||||
const [desc, setDesc] = useState('');
|
||||
const [assignee, setAssignee] = useState('u1');
|
||||
const [assignee, setAssignee] = useState(users[0]?.id || '');
|
||||
const [priority, setPriority] = useState<Priority>('medium');
|
||||
const [status, setStatus] = useState<Status>(defaultStatus || 'todo');
|
||||
const [dueDate, setDueDate] = useState(defaultDate || new Date().toISOString().split('T')[0]);
|
||||
@@ -173,7 +176,7 @@ export function AddTaskModal({ onClose, onAdd, defaultDate, defaultStatus }: Mod
|
||||
if (!title.trim()) { setError(true); return; }
|
||||
const task: Task = {
|
||||
id: `t${Date.now()}`, title, description: desc, status, priority,
|
||||
assignee, reporter: 'u1', dueDate,
|
||||
assignee, reporter: currentUser.id, dueDate,
|
||||
tags: tags ? tags.split(',').map(t => t.trim()).filter(Boolean) : [],
|
||||
subtasks: [], comments: [],
|
||||
activity: [{ id: `a${Date.now()}`, text: `📝 Task created`, timestamp: new Date().toISOString() }],
|
||||
@@ -199,7 +202,7 @@ export function AddTaskModal({ onClose, onAdd, defaultDate, defaultStatus }: Mod
|
||||
<div className="modal-field">
|
||||
<label>Assignee</label>
|
||||
<select className="modal-input" value={assignee} onChange={e => setAssignee(e.target.value)}>
|
||||
{USERS.map(u => <option key={u.id} value={u.id}>{u.avatar} {u.name}</option>)}
|
||||
{users.map(u => <option key={u.id} value={u.id}>{u.avatar} {u.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className="modal-field">
|
||||
|
||||
Reference in New Issue
Block a user