diff --git a/backend/controllers/analytics.controller.js b/backend/controllers/analytics.controller.js index e4e13fd..a5ac27e 100644 --- a/backend/controllers/analytics.controller.js +++ b/backend/controllers/analytics.controller.js @@ -1,106 +1,77 @@ import supabase from "../config/db.js"; -const memberProfiles = [ - { id: "alex", name: "Alex Rivera", role: "Lead Frontend Engineer", focus: "Frontend", image: "https://i.pravatar.cc/96?img=11" }, - { id: "jordan", name: "Jordan Smith", role: "Backend Engineer", focus: "API", image: "https://i.pravatar.cc/96?img=12" }, - { id: "casey", name: "Casey Morgan", role: "Fullstack Developer", focus: "Fullstack", image: "https://i.pravatar.cc/96?img=13" }, - { id: "riley", name: "Riley Lee", role: "QA Analyst", focus: "QA", image: "https://i.pravatar.cc/96?img=14" }, - { id: "morgan", name: "Morgan Patel", role: "Product Engineer", focus: "Product", image: "https://i.pravatar.cc/96?img=15" }, - { id: "quinn", name: "Quinn Taylor", role: "DevOps Engineer", focus: "Ops", image: "https://i.pravatar.cc/96?img=16" }, -]; - -function hashString(value) { - return String(value || "") - .split("") - .reduce((total, char) => total + char.charCodeAt(0), 0); -} - -function buildMembers(tasks = [], messages = [], sprintOffset = 0) { - const taskAssignments = Object.create(null); - const completedAssignments = Object.create(null); - const messageAssignments = Object.create(null); - - tasks.forEach((task, taskIndex) => { - const seed = hashString(task.id || task.title || taskIndex); - const memberIndex = seed % memberProfiles.length; +export const getAnalytics = async (req, res) => { + try { + const [ + { data: tasks, error: tasksError }, + { data: messages, error: messagesError }, + ] = await Promise.all([ + supabase.from("tasks").select("id,title,status,position,created_at"), + supabase.from("messages").select("id,username,text,created_at"), + ]); - taskAssignments[memberIndex] = - (taskAssignments[memberIndex] || 0) + 1; + if (tasksError) throw tasksError; + if (messagesError) throw messagesError; - if (task.status === "done") { - completedAssignments[memberIndex] = - (completedAssignments[memberIndex] || 0) + 1; + // Build a roster of real contributors by collecting every distinct username + // that appears in the messages table. For each contributor compute metrics + // derived entirely from the real data stored in Supabase. + const usernameSet = new Set(); + for (const msg of messages || []) { + if (msg.username) usernameSet.add(msg.username); } - }); - - messages.forEach((message) => { - const seed = hashString(message.username || message.id); - const memberIndex = seed % memberProfiles.length; - - messageAssignments[memberIndex] = - (messageAssignments[memberIndex] || 0) + 1; - }); - - return memberProfiles.map((profile, index) => { - const assignedTasks = taskAssignments[index] || 0; - const completedTasks = completedAssignments[index] || 0; - const messageCount = messageAssignments[index] || 0; - - const assigned = Math.min( - 98, - Math.max(35, assignedTasks * 12 + 48 - sprintOffset * 6) - ); - const completed = Math.min( - assigned, - Math.max( - 20, - completedTasks * 18 + messageCount * 4 + 34 - sprintOffset * 5 - ) - ); - - const reviews = Math.max( - 6, - messageCount * 2 + completedTasks + 8 - sprintOffset - ); - - const activity = Array.from({ length: 8 }, (_, day) => { - const base = - assigned + - completed + - reviews + - index * 9 + - day * 11 - - sprintOffset * 7; - - return Math.min(100, Math.max(18, base % 100)); - }); - - return { - ...profile, - assigned, - completed, - reviews, - activity, + // If no messages exist yet, include a placeholder so the UI is not empty. + const usernames = usernameSet.size > 0 ? Array.from(usernameSet) : ["team"]; + + const buildMembers = (sprintTasks = []) => { + return usernames.map((username) => { + const userMessages = (messages || []).filter( + (m) => m.username === username + ); + + // Tasks are not directly linked to users in the current schema, so + // distribute them evenly across contributors as a best-effort metric + // until per-user task assignment is available in the data model. + const index = usernames.indexOf(username); + const assignedTasks = sprintTasks.filter( + (_, i) => i % usernames.length === index + ); + const completedTasks = assignedTasks.filter( + (t) => t.status === "done" + ); + + // Build a 8-day activity histogram from real message timestamps. + const activity = Array.from({ length: 8 }, (_, day) => { + const cutoff = new Date(); + cutoff.setDate(cutoff.getDate() - (7 - day)); + const dayStart = new Date(cutoff); + dayStart.setHours(0, 0, 0, 0); + const dayEnd = new Date(cutoff); + dayEnd.setHours(23, 59, 59, 999); + return userMessages.filter((m) => { + const t = new Date(m.created_at); + return t >= dayStart && t <= dayEnd; + }).length; + }); + + return { + name: username, + assigned: assignedTasks.length, + completed: completedTasks.length, + reviews: userMessages.length, + activity, + }; + }); }; - }); -} -export const getAnalytics = async (req, res) => { - try { - const [{ data: tasks, error: tasksError }, { data: messages, error: messagesError }] = - await Promise.all([ - supabase.from("tasks").select("id,title,status,position,created_at"), - supabase.from("messages").select("id,username,text,created_at"), - ]); - - if (tasksError) throw tasksError; - if (messagesError) throw messagesError; + const allTasks = tasks || []; + const halfLen = Math.ceil(allTasks.length / 2); res.status(200).json({ sprints: [ - { label: "Sprint 42", members: buildMembers(tasks || [], messages || [], 0) }, - { label: "Sprint 41", members: buildMembers(tasks || [], messages || [], 1) }, + { label: "Current Sprint", members: buildMembers(allTasks.slice(halfLen)) }, + { label: "Previous Sprint", members: buildMembers(allTasks.slice(0, halfLen)) }, ], generatedAt: new Date().toISOString(), });