import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../models/profile.model.dart'; import '../../../models/programmer_day_sheet.model.dart'; import '../../../models/programmer_task.model.dart'; import '../../../providers/leave_provider.dart'; import '../../../providers/profile_provider.dart'; import '../../../providers/programmer_day_data_provider.dart'; import '../../../providers/programmer_day_sheets_provider.dart'; import '../../../providers/programmer_tasks_provider.dart'; import '../../../utils/app_time.dart'; import '../../../utils/programmer_daily_time.dart'; import '../../../utils/programmer_day_rows.dart'; import '../../../utils/programmer_stats.dart'; import '../../../utils/staff_presence.dart'; import 'badges_strip.dart'; import 'now_working_card.dart'; import 'stat_tiles.dart'; import 'task_sections.dart'; /// The programmer's own work dashboard — hero card, stat tiles (Task 8), and /// task sections. Drop it into the Mine tab with `userId: me`. class ProgrammerDashboard extends ConsumerWidget { const ProgrammerDashboard({ super.key, required this.userId, this.readOnly = false, }); final String userId; final bool readOnly; @override Widget build(BuildContext context, WidgetRef ref) { // Drive 30-second rebuilds so duration values stay fresh. ref.watch(clockTickerProvider); final tasksAsync = ref.watch(programmerTasksProvider); final runStates = ref.watch(programmerRunStatesProvider); final todayKey = ref.watch(todayKeyProvider); final dayDataAsync = ref.watch(programmerDayDataProvider(todayKey)); final profilesAsync = ref.watch(profilesProvider); final now = ref.watch(clockTickerProvider).valueOrNull ?? AppTime.now(); final sheetsAsync = ref.watch(daySheetsForUserProvider(userId)); final sheets = sheetsAsync.valueOrNull ?? const []; final liveHelpThisMonthSec = ref.watch(helpGivenThisMonthProvider(userId)).valueOrNull; final tasks = tasksAsync.valueOrNull ?? const []; final profileById = { for (final p in profilesAsync.valueOrNull ?? []) p.id: p, }; final profileNames = { for (final p in profilesAsync.valueOrNull ?? []) p.id: p.fullName, }; final dayData = dayDataAsync.valueOrNull; final leaveDays = approvedLeaveDays( ref.watch(leavesProvider).valueOrNull ?? [], userId, ); final todayRows = dayData == null ? const [] : buildDayRows( userId: userId, day: todayKey, tasks: tasks, logsByTask: dayData.logsByTask, workLogs: dayData.workLogs, projectNames: const {}, profileNames: profileNames, now: now, ); final todayFocus = Duration(seconds: totalSeconds(todayRows)); final stats = computeProgrammerStats( userId: userId, now: now, tasks: tasks, sheets: sheets, leaveDays: leaveDays, todayFocus: todayFocus, liveHelpThisMonthSec: liveHelpThisMonthSec, ); final showSheetStats = ref.watch(currentProfileProvider).valueOrNull?.role == 'programmer'; final badges = computeBadges(stats, includeSheetBadges: showSheetStats); final groups = groupTasksForDashboard(tasks, runStates, userId); final runningTask = groups.running.isNotEmpty ? groups.running.first : null; final nextUp = groups.notStarted.isNotEmpty ? groups.notStarted.first : null; final todayOnTask = _computeTodayDuration( runningTask: runningTask, dayDataAsync: dayDataAsync, userId: userId, todayKey: todayKey, ); final totalOnTask = _computeTotalDuration( runningTask: runningTask, dayDataAsync: dayDataAsync, userId: userId, ); return ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), children: [ NowWorkingCard( running: runningTask, nextUp: nextUp, todayOnTask: todayOnTask, totalOnTask: totalOnTask, readOnly: readOnly, ), const SizedBox(height: 16), StatTiles(stats: stats, showSheetStats: showSheetStats), const SizedBox(height: 8), BadgesStrip(badges: badges), const SizedBox(height: 16), TaskSections( groups: groups, profileById: profileById, readOnly: readOnly, ), ], ); } Duration _computeTodayDuration({ required ProgrammerTask? runningTask, required AsyncValue dayDataAsync, required String userId, required DateTime todayKey, }) { if (runningTask == null) return Duration.zero; final dayData = dayDataAsync.valueOrNull; if (dayData == null) return Duration.zero; final logs = dayData.logsByTask[runningTask.id] ?? const []; final dailyByAssignee = dailySecondsByAssignee( task: runningTask, logs: logs, now: DateTime.now().toUtc(), ); final secs = dailyByAssignee[userId]?[todayKey] ?? 0; return Duration(seconds: secs); } Duration _computeTotalDuration({ required ProgrammerTask? runningTask, required AsyncValue dayDataAsync, required String userId, }) { if (runningTask == null) return Duration.zero; final dayData = dayDataAsync.valueOrNull; if (dayData == null) return Duration.zero; final logs = dayData.logsByTask[runningTask.id] ?? const []; final dailyByAssignee = dailySecondsByAssignee( task: runningTask, logs: logs, now: DateTime.now().toUtc(), ); final totalSecs = (dailyByAssignee[userId] ?? const {}).values.fold(0, (s, v) => s + v); return Duration(seconds: totalSecs); } }