// ============ 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;