112 lines
7.1 KiB
TypeScript
112 lines
7.1 KiB
TypeScript
import React, { useState } from 'react';
|
|
import type { Task, User } from './data';
|
|
import { PRIORITY_COLORS } from './data';
|
|
import { Avatar, StatusBadge } from './Shared';
|
|
|
|
export function TeamTasksPage({ tasks, users }: { tasks: Task[]; currentUser: User; users: User[] }) {
|
|
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
|
|
|
|
return (
|
|
<div className="team-tasks">
|
|
<h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 16 }}>Team Tasks</h2>
|
|
{users.map(m => {
|
|
const mTasks = tasks.filter(t => t.assignee === m.id);
|
|
const isOpen = expanded[m.id] !== false;
|
|
return (
|
|
<div key={m.id} className="team-group">
|
|
<div className="team-group-header" onClick={() => setExpanded(e => ({ ...e, [m.id]: !isOpen }))}>
|
|
<Avatar userId={m.id} size={28} users={users} />
|
|
<span className="team-group-name">{m.name}</span>
|
|
<span className="team-group-count">({mTasks.length} tasks)</span>
|
|
<span style={{ color: '#64748b' }}>{isOpen ? '▼' : '▶'}</span>
|
|
</div>
|
|
{isOpen && (
|
|
<div className="team-group-tasks">
|
|
{mTasks.map(t => (
|
|
<div key={t.id} className="team-task-row">
|
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: PRIORITY_COLORS[t.priority].color }} />
|
|
<span className="team-task-title">{t.title}</span>
|
|
<StatusBadge status={t.status} />
|
|
<span style={{ fontSize: 11, color: '#64748b' }}>
|
|
{new Date(t.dueDate + 'T00:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
|
</span>
|
|
{t.subtasks.length > 0 && <span style={{ fontSize: 10, color: '#64748b' }}>📋 {t.subtasks.filter(s => s.done).length}/{t.subtasks.length}</span>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function MembersPage({ tasks, users }: { tasks: Task[]; users: User[] }) {
|
|
const [expanded, setExpanded] = useState<string | null>(null);
|
|
const [showInvite, setShowInvite] = useState(false);
|
|
|
|
return (
|
|
<div className="members-page">
|
|
<div className="members-header">
|
|
<h2>Team Members</h2>
|
|
<button className="btn-ghost" onClick={() => setShowInvite(true)}>+ Invite Member</button>
|
|
</div>
|
|
<table className="members-table">
|
|
<thead><tr><th>Avatar</th><th>Full Name</th><th>Role</th><th>Dept</th><th>Assigned</th><th>Done</th><th>Active</th></tr></thead>
|
|
<tbody>
|
|
{users.map(u => {
|
|
const ut = tasks.filter(t => t.assignee === u.id);
|
|
const done = ut.filter(t => t.status === 'done').length;
|
|
const active = ut.filter(t => t.status !== 'done').length;
|
|
const roleColors: Record<string, string> = { ceo: '#eab308', cto: '#818cf8', manager: '#fb923c', tech_lead: '#06b6d4', scrum_master: '#a855f7', product_owner: '#ec4899', designer: '#f43f5e', qa: '#14b8a6', employee: '#22c55e' };
|
|
return (
|
|
<React.Fragment key={u.id}>
|
|
<tr onClick={() => setExpanded(expanded === u.id ? null : u.id)}>
|
|
<td><Avatar userId={u.id} size={28} users={users} /></td>
|
|
<td>{u.name}</td>
|
|
<td><span style={{ background: `${roleColors[u.role]}22`, color: roleColors[u.role], padding: '2px 8px', borderRadius: 10, fontSize: 10, fontWeight: 600 }}>{u.role.toUpperCase()}</span></td>
|
|
<td>{u.dept}</td>
|
|
<td>{ut.length}</td>
|
|
<td>{done}</td>
|
|
<td>{active}</td>
|
|
</tr>
|
|
{expanded === u.id && (
|
|
<tr><td colSpan={7}>
|
|
<div className="member-expand">
|
|
{ut.map(t => (
|
|
<div key={t.id} className="team-task-row">
|
|
<span style={{ width: 8, height: 8, borderRadius: '50%', background: PRIORITY_COLORS[t.priority].color }} />
|
|
<span className="team-task-title">{t.title}</span>
|
|
<StatusBadge status={t.status} />
|
|
</div>
|
|
))}
|
|
{ut.length === 0 && <span style={{ color: '#64748b', fontSize: 12 }}>No tasks assigned</span>}
|
|
</div>
|
|
</td></tr>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
|
|
{showInvite && (
|
|
<div className="modal-backdrop" onClick={() => setShowInvite(false)}>
|
|
<div className="modal invite-modal" onClick={e => e.stopPropagation()}>
|
|
<div className="modal-header"><h2>Invite Member</h2><button className="drawer-close" onClick={() => setShowInvite(false)}>✕</button></div>
|
|
<div className="modal-body">
|
|
<div className="modal-field"><label>Email</label><input className="modal-input" placeholder="member@company.io" /></div>
|
|
<div className="modal-field">
|
|
<label>Role</label>
|
|
<select className="modal-input"><option value="employee">Employee</option><option value="tech_lead">Tech Lead</option><option value="scrum_master">Scrum Master</option><option value="product_owner">Product Owner</option><option value="designer">Designer</option><option value="qa">QA Engineer</option><option value="manager">Manager</option><option value="cto">CTO</option><option value="ceo">CEO</option></select>
|
|
</div>
|
|
</div>
|
|
<div className="modal-footer"><button className="btn-ghost" onClick={() => setShowInvite(false)}>Cancel</button><button className="btn-primary" onClick={() => setShowInvite(false)}>Send Invite</button></div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|