// ============ TaskQuest — Summary section & leaderboard ============ const { ROLES: R3, PROJECTS: P3, CONSULTANTS: C3 } = window.TQ; function StatCard({ icon, iconColor, label, children, foot }) { return (
{React.createElement(icon, { size: 15 })} {label}
{children} {foot &&
{foot}
}
); } function MiniLeaderboard({ board, meKey, label, scopeName, metric, headerRight, headline }) { // metric: undefined/"both" → "X pts · Y%"; "points" → ranked by points; "perf" → ranked by performance const rows = metric === "perf" ? board.slice().sort((a, b) => (b.perf - a.perf) || (b.total - a.total)).map((s, i) => ({ ...s, _rank: i + 1 })) : board.map(s => ({ ...s, _rank: s.rank })); return (
{label} {headerRight ? {headerRight} : {scopeName}}
{headline}
{rows.map(s => (
{s._rank === 1 ? : s._rank} {R3[s.key].name}{s.key === meKey ? " (You)" : ""} {metric === "perf" ? <>{s.perf} % : metric === "points" ? <>{(s.avg || 0).toFixed(1)} avg : <>{(s.avg || 0).toFixed(1)} avg · {s.perf}%}
))}
); } function SummarySection({ role, project, tasks, bonus }) { const { leaderboard, computeScores } = window.TQ; const r = R3[role]; const scope = project; const scopeName = scope === "all" ? "All projects" : (P3[scope] ? P3[scope].name : ""); const board = leaderboard(tasks, bonus, scope); const scopedTasks = scope === "all" ? tasks : tasks.filter(t => t.project === scope); const pendingApprovals = scopedTasks.filter(t => (t.request && t.request.status === "pending") || (t.completion && t.completion.status === "pending") ).length; if (r.type === "consultant") { // A consultant's board shows ALL their tasks regardless of the project // switcher, so their stats + rank must span every project too (otherwise // points earned on another project's deliverable silently read as zero). const cBoard = leaderboard(tasks, bonus, "all"); const me = cBoard.find(s => s.key === role) || { total: 0, avg: 0, perf: 0, rank: "—", ontime: 0, completed: 0, assigned: 0, bonus: 0 }; return (
{me.total} pts over {me.completed} completed{me.bonus > 0 ? <> · +{me.bonus} bonus : null} : <>No completed tasks yet}>
{(me.avg || 0).toFixed(1)}pts
{me.perf}%
#{me.rank}
); } // Manager aggregate stats const totalPts = board.reduce((a, s) => a + s.total, 0); const completedAll = board.reduce((a, s) => a + s.completed, 0); const assignedAll = board.reduce((a, s) => a + s.assigned, 0); const teamAvg = completedAll ? totalPts / completedAll : 0; const teamPerf = assignedAll ? Math.round((completedAll / assignedAll) * 100) : 0; const onTrack = scopedTasks.filter(t => t.status === "happy").length; return (
{totalPts} pts over {completedAll} completed : `${onTrack} tasks on the happy path`}>
{(teamAvg || 0).toFixed(1)}pts
{teamPerf}%
{pendingApprovals}
); } window.SummarySection = SummarySection; window.StatCard = StatCard; window.MiniLeaderboard = MiniLeaderboard;