Files
scrum-manager/src/ListView.tsx
tusuii e46d8773ee 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
2026-02-15 11:36:38 +05:30

89 lines
5.0 KiB
TypeScript

import { useState } from 'react';
import type { Task, User } from './data';
import { getUserById } from './data';
import { Avatar, PriorityBadge, StatusBadge } from './Shared';
interface ListProps {
tasks: Task[]; currentUser: User; onTaskClick: (t: Task) => void;
filterUser: string | null; searchQuery: string;
onToggleDone: (taskId: string) => void;
}
type SortKey = 'dueDate' | 'priority' | 'status' | 'assignee';
const PRIO_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
export function ListView({ tasks, currentUser, onTaskClick, filterUser, searchQuery, onToggleDone }: ListProps) {
const [sortBy, setSortBy] = useState<SortKey>('dueDate');
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const [menuOpen, setMenuOpen] = useState<string | null>(null);
let filtered = tasks;
if (currentUser.role === 'employee') filtered = filtered.filter(t => t.assignee === currentUser.id);
if (filterUser) filtered = filtered.filter(t => t.assignee === filterUser);
if (searchQuery) filtered = filtered.filter(t => t.title.toLowerCase().includes(searchQuery.toLowerCase()));
const sorted = [...filtered].sort((a, b) => {
let cmp = 0;
if (sortBy === 'dueDate') cmp = a.dueDate.localeCompare(b.dueDate);
else if (sortBy === 'priority') cmp = PRIO_ORDER[a.priority] - PRIO_ORDER[b.priority];
else if (sortBy === 'status') cmp = a.status.localeCompare(b.status);
else if (sortBy === 'assignee') cmp = a.assignee.localeCompare(b.assignee);
return sortDir === 'asc' ? cmp : -cmp;
});
const toggleSort = (key: SortKey) => {
if (sortBy === key) setSortDir(d => d === 'asc' ? 'desc' : 'asc');
else { setSortBy(key); setSortDir('asc'); }
};
return (
<div className="list-view">
<div className="list-sort-row">
Sort by:
{(['dueDate', 'priority', 'status', 'assignee'] as SortKey[]).map(k => (
<button key={k} className={`list-sort-btn ${sortBy === k ? 'active' : ''}`} onClick={() => toggleSort(k)}>
{k === 'dueDate' ? 'Due Date' : k.charAt(0).toUpperCase() + k.slice(1)}
{sortBy === k && (sortDir === 'asc' ? ' ↑' : ' ↓')}
</button>
))}
</div>
<table className="list-table">
<thead>
<tr><th></th><th>Title</th><th>Assignee</th><th>Priority</th><th>Status</th><th>Due Date</th><th>Tags</th><th>Actions</th></tr>
</thead>
<tbody>
{sorted.map(t => {
const u = getUserById(t.assignee);
const due = new Date(t.dueDate + 'T00:00:00');
const overdue = due < new Date() && t.status !== 'done';
return (
<tr key={t.id}>
<td><input type="checkbox" checked={t.status === 'done'} onChange={() => onToggleDone(t.id)} /></td>
<td onClick={() => onTaskClick(t)} style={{ cursor: 'pointer' }}>{t.title}</td>
<td><div style={{ display: 'flex', alignItems: 'center', gap: 6 }}><Avatar userId={t.assignee} size={20} />{u?.name}</div></td>
<td><PriorityBadge level={t.priority} /></td>
<td><StatusBadge status={t.status} /></td>
<td style={{ color: overdue ? '#ef4444' : undefined }}>{due.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</td>
<td>
{t.tags.slice(0, 2).map(tag => <span key={tag} className="tag-pill" style={{ marginRight: 4 }}>{tag}</span>)}
{t.tags.length > 2 && <span className="tag-pill">+{t.tags.length - 2}</span>}
</td>
<td style={{ position: 'relative' }}>
<button className="list-actions-btn" onClick={e => { e.stopPropagation(); setMenuOpen(menuOpen === t.id ? null : t.id); }}></button>
{menuOpen === t.id && (
<div className="list-dropdown">
<button className="list-dropdown-item" onClick={() => { onTaskClick(t); setMenuOpen(null); }}>Edit</button>
<button className="list-dropdown-item" onClick={() => setMenuOpen(null)}>Delete</button>
<button className="list-dropdown-item" onClick={() => setMenuOpen(null)}>Copy Link</button>
</div>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}