import 'package:tasq/models/leave_of_absence.model.dart'; import 'package:tasq/models/pass_slip.model.dart'; import 'package:tasq/models/profile.model.dart'; import 'package:tasq/models/programmer_task.model.dart'; import 'package:tasq/models/programmer_task_work_log.model.dart'; import 'package:tasq/providers/programmer_day_data_provider.dart'; import 'package:tasq/utils/programmer_day_rows.dart'; enum MonitorState { working, paused, idle, onLeave, passSlip } class HelpEdge { final String helperId; final String assigneeId; final String taskId; final String taskTitle; final int minutes; final DateTime at; HelpEdge({ required this.helperId, required this.assigneeId, required this.taskId, required this.taskTitle, required this.minutes, required this.at, }); } /// Build help edges from work logs for a given day. /// /// Returns edges where: /// - author != assignee (author is helping) /// - minutes > 0 /// - workDate == day /// /// Sorted by createdAt newest first. List buildHelpEdges({ required List workLogs, required Map taskById, required DateTime day, }) { final edges = []; for (final log in workLogs) { final task = taskById[log.taskId]; if (task == null) continue; // Filter: author != assignee AND minutes > 0 AND workDate == day if (log.authorId == task.assigneeId) continue; if (log.minutes == null || log.minutes! <= 0) continue; if (log.workDate != day) continue; edges.add(HelpEdge( helperId: log.authorId, assigneeId: task.assigneeId ?? '', taskId: log.taskId, taskTitle: task.title, minutes: log.minutes!, at: log.createdAt, )); } // Sort by createdAt newest first edges.sort((a, b) => b.at.compareTo(a.at)); return edges; } class ProgrammerMonitorEntry { final Profile programmer; final MonitorState state; final ProgrammerTask? currentTask; final Duration currentTaskToday; final Duration todayTotal; final int inProgress; final int paused; final int notStarted; final String? sheetStatus; final List helped; final List helpedBy; ProgrammerMonitorEntry({ required this.programmer, required this.state, required this.currentTask, required this.currentTaskToday, required this.todayTotal, required this.inProgress, required this.paused, required this.notStarted, this.sheetStatus, required this.helped, required this.helpedBy, }); } /// Build monitor entries for all programmers for a given day. /// /// Sort order: working -> paused -> idle -> passSlip -> onLeave, /// then alphabetical by programmer.fullName within each state group. /// /// Parameters [leaves] and [passSlips] should be pre-computed maps from /// activeLeaveByUser() and activePassSlipByUser() respectively. List buildMonitorEntries({ required List programmers, required DayActivityData today, required Map runStates, required Map leaves, required Map passSlips, required Map sheetStatusByUser, required Map projectNames, required Map profileNames, required DateTime now, }) { final taskById = {for (final t in today.tasks) t.id: t}; final entries = []; for (final programmer in programmers) { final programmerId = programmer.id; // Step 1: Determine state // Priority: onLeave > passSlip > working/paused/idle MonitorState state; if (leaves.containsKey(programmerId)) { state = MonitorState.onLeave; } else if (passSlips.containsKey(programmerId)) { state = MonitorState.passSlip; } else { // Determine from task states state = _determineState(programmerId, today.tasks, runStates); } // Step 2: Find current task (oldest running task) ProgrammerTask? currentTask; if (state == MonitorState.working) { currentTask = _findCurrentTask(programmerId, today.tasks, runStates); } // Step 3: Count tasks by state final (inProgress, paused, notStarted) = _countTasksByState(programmerId, today.tasks, runStates); // Step 4: Calculate durations final allTasksRows = buildDayRows( userId: programmerId, day: today.day, tasks: today.tasks, logsByTask: today.logsByTask, workLogs: today.workLogs, projectNames: projectNames, profileNames: profileNames, now: now, ); final todayTotal = Duration(seconds: totalSeconds(allTasksRows)); // For current task, filter to only that task's work logs final currentTaskToday = _calculateCurrentTaskDuration( programmerId, currentTask, today, projectNames, profileNames, now, ); // Step 5: Build helped and helpedBy edges final helped = _buildHelpedEdges(programmerId, today.workLogs, taskById, today.day); final helpedBy = _buildHelpedByEdges(programmerId, today.workLogs, taskById, today.day); entries.add(ProgrammerMonitorEntry( programmer: programmer, state: state, currentTask: currentTask, currentTaskToday: currentTaskToday, todayTotal: todayTotal, inProgress: inProgress, paused: paused, notStarted: notStarted, sheetStatus: sheetStatusByUser[programmerId], helped: helped, helpedBy: helpedBy, )); } // Sort by state then by name entries.sort((a, b) { final stateOrder = [ MonitorState.working, MonitorState.paused, MonitorState.idle, MonitorState.passSlip, MonitorState.onLeave, ]; final aStateIdx = stateOrder.indexOf(a.state); final bStateIdx = stateOrder.indexOf(b.state); if (aStateIdx != bStateIdx) { return aStateIdx.compareTo(bStateIdx); } return a.programmer.fullName.compareTo(b.programmer.fullName); }); return entries; } /// Determine state from task states MonitorState _determineState( String programmerId, List tasks, Map runStates, ) { final programmerTasks = tasks.where((t) => t.assigneeId == programmerId).toList(); var hasRunning = false; var hasPaused = false; for (final task in programmerTasks) { final runInfo = runStates[task.id]; if (runInfo == null) continue; if (runInfo.state == ProgrammerRunState.running) { hasRunning = true; } else if (runInfo.state == ProgrammerRunState.paused) { hasPaused = true; } } if (hasRunning) return MonitorState.working; if (hasPaused) return MonitorState.paused; return MonitorState.idle; } /// Find the current task (oldest running task by since) ProgrammerTask? _findCurrentTask( String programmerId, List tasks, Map runStates, ) { final runningTasks = tasks .where((t) => t.assigneeId == programmerId && runStates[t.id]?.state == ProgrammerRunState.running) .toList(); if (runningTasks.isEmpty) return null; // Sort by since (earliest first) runningTasks.sort((a, b) { final aSince = runStates[a.id]?.since ?? DateTime(0); final bSince = runStates[b.id]?.since ?? DateTime(0); return aSince.compareTo(bSince); }); return runningTasks.first; } /// Count tasks by state (int inProgress, int paused, int notStarted) _countTasksByState( String programmerId, List tasks, Map runStates, ) { var inProgress = 0; var paused = 0; var notStarted = 0; final programmerTasks = tasks.where((t) => t.assigneeId == programmerId).toList(); for (final task in programmerTasks) { final runInfo = runStates[task.id]; if (runInfo == null) continue; if (runInfo.state == ProgrammerRunState.running || runInfo.state == ProgrammerRunState.paused) { // This is still "in progress" (running or paused) // We count these separately if (runInfo.state == ProgrammerRunState.running) { inProgress++; } else { paused++; } } else if (runInfo.state == ProgrammerRunState.notStarted) { notStarted++; } } return (inProgress, paused, notStarted); } /// Calculate duration for current task Duration _calculateCurrentTaskDuration( String programmerId, ProgrammerTask? currentTask, DayActivityData today, Map projectNames, Map profileNames, DateTime now, ) { if (currentTask == null) return Duration.zero; // Filter work logs to only this task final currentTaskLogs = today.workLogs.where((w) => w.taskId == currentTask.id).toList(); // Filter activity logs to only this task final currentTaskActivityLogs = { currentTask.id: today.logsByTask[currentTask.id] ?? [] }; final rows = buildDayRows( userId: programmerId, day: today.day, tasks: [currentTask], logsByTask: currentTaskActivityLogs, workLogs: currentTaskLogs, projectNames: projectNames, profileNames: profileNames, now: now, ); return Duration(seconds: totalSeconds(rows)); } /// Build helped edges (where programmer helped others) List _buildHelpedEdges( String programmerId, List workLogs, Map taskById, DateTime day, ) { final edges = []; for (final log in workLogs) { // Filter: author == programmer AND author != assignee AND minutes > 0 AND workDate == day if (log.authorId != programmerId) continue; final task = taskById[log.taskId]; if (task == null) continue; if (log.authorId == task.assigneeId) continue; if (log.minutes == null || log.minutes! <= 0) continue; if (log.workDate != day) continue; edges.add(HelpEdge( helperId: log.authorId, assigneeId: task.assigneeId ?? '', taskId: log.taskId, taskTitle: task.title, minutes: log.minutes!, at: log.createdAt, )); } // Sort by createdAt newest first edges.sort((a, b) => b.at.compareTo(a.at)); return edges; } /// Build helpedBy edges (where others helped programmer) List _buildHelpedByEdges( String programmerId, List workLogs, Map taskById, DateTime day, ) { final edges = []; for (final log in workLogs) { final task = taskById[log.taskId]; if (task == null) continue; // Filter: task assigned to programmer AND author != programmer AND minutes > 0 AND workDate == day if (task.assigneeId != programmerId) continue; if (log.authorId == programmerId) continue; if (log.minutes == null || log.minutes! <= 0) continue; if (log.workDate != day) continue; edges.add(HelpEdge( helperId: log.authorId, assigneeId: programmerId, taskId: log.taskId, taskTitle: task.title, minutes: log.minutes!, at: log.createdAt, )); } // Sort by createdAt newest first edges.sort((a, b) => b.at.compareTo(a.at)); return edges; }