feat: complete Scrum-manager MVP — dark-themed multi-user task manager

- Login with role-based auth (CTO/Manager/Employee)
- Calendar view (month/week) with task chips and quick-add
- Kanban board with status columns
- Sortable list view with action menus
- Task detail drawer with subtasks, comments, activity
- Add task modal with validation
- Dashboard with stats, workload, priority breakdown
- Team tasks grouped by assignee
- Reports page with recharts (bar, pie, line, horizontal bar)
- Members page with invite modal
- Search and assignee filter across views
- ErrorBoundary for production error handling
- Full dark design system via index.css
This commit is contained in:
tusuii
2026-02-15 11:36:38 +05:30
commit e46d8773ee
26 changed files with 5410 additions and 0 deletions

113
src/Pages.tsx Normal file
View File

@@ -0,0 +1,113 @@
import React, { useState } from 'react';
import type { Task, User } from './data';
import { USERS, PRIORITY_COLORS } from './data';
import { Avatar, StatusBadge } from './Shared';
export function TeamTasksPage({ tasks }: { tasks: Task[]; currentUser: User }) {
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
const members = USERS;
return (
<div className="team-tasks">
<h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 16 }}>Team Tasks</h2>
{members.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} />
<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 }: { tasks: Task[] }) {
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> = { cto: '#818cf8', manager: '#fb923c', employee: '#22c55e' };
return (
<React.Fragment key={u.id}>
<tr onClick={() => setExpanded(expanded === u.id ? null : u.id)}>
<td><Avatar userId={u.id} size={28} /></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="manager">Manager</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>
);
}