From 2946945ce329084113cece289c76967b967ba59d Mon Sep 17 00:00:00 2001 From: Marc Rejohn Castillano Date: Mon, 28 Sep 2026 22:25:25 +0800 Subject: [PATCH] fix(programmer-tasks): tab switching + help-given live data Tab switching: _buildTabs was recreating TabController on every build because `_tabs == tabs` compares List by reference identity, not content. programmerTasksTabsFor() always returns a new list, so the guard was always false, resetting the controller to initialIndex 0 on every tap. Fixed by using listEquals from flutter/foundation. Help given: computeProgrammerStats accumulated helpGivenThisMonth only from approvedSnapshot rows, which only exist after admin approval. Programmers who helped others but had pending sheets saw 0m indefinitely. Fixed by adding helpGivenThisMonthProvider (FutureProvider.family) that queries programmer_task_work_logs directly for the current Manila month, and passing it as liveHelpThisMonthSec to computeProgrammerStats. Co-Authored-By: claude-flow --- .../programmer_day_data_provider.dart | 29 +++++++++++++++++++ .../dashboard/programmer_dashboard.dart | 4 +++ .../programmer_tasks_list_screen.dart | 5 ++-- lib/utils/programmer_stats.dart | 8 ++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/providers/programmer_day_data_provider.dart b/lib/providers/programmer_day_data_provider.dart index 51bae744..aa4ab298 100644 --- a/lib/providers/programmer_day_data_provider.dart +++ b/lib/providers/programmer_day_data_provider.dart @@ -200,6 +200,35 @@ final programmerDayDataProvider = return fetchDayActivityData(client, day: day, tasks: tasks, now: AppTime.now()); }); +/// Total help-given seconds for [userId] in the current Manila calendar month. +/// +/// Queries work logs with minutes > 0 authored by [userId] this month. +/// Re-fetches whenever [programmerActivitySignalProvider] fires. +final helpGivenThisMonthProvider = + FutureProvider.family((ref, userId) async { + ref.watch(programmerActivitySignalProvider); + final client = ref.watch(supabaseClientProvider); + final today = ref.watch(todayKeyProvider); + final monthStart = DateTime.utc(today.year, today.month, 1); + final nextMonthStart = today.month < 12 + ? DateTime.utc(today.year, today.month + 1, 1) + : DateTime.utc(today.year + 1, 1, 1); + + final raw = await client + .from('programmer_task_work_logs') + .select('minutes') + .eq('author_id', userId) + .gte('work_date', formatDayKey(monthStart)) + .lt('work_date', formatDayKey(nextMonthStart)) as List; + + int totalSec = 0; + for (final r in raw) { + final minutes = r['minutes'] as int?; + if (minutes != null && minutes > 0) totalSec += minutes * 60; + } + return totalSec; +}); + /// Run states for today's tasks, derived from today's [programmerDayDataProvider]. final programmerRunStatesProvider = Provider>((ref) { final today = ref.watch(todayKeyProvider); diff --git a/lib/screens/programmer_tasks/dashboard/programmer_dashboard.dart b/lib/screens/programmer_tasks/dashboard/programmer_dashboard.dart index db9c84b4..d39957db 100644 --- a/lib/screens/programmer_tasks/dashboard/programmer_dashboard.dart +++ b/lib/screens/programmer_tasks/dashboard/programmer_dashboard.dart @@ -47,6 +47,9 @@ class ProgrammerDashboard extends ConsumerWidget { 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, @@ -82,6 +85,7 @@ class ProgrammerDashboard extends ConsumerWidget { sheets: sheets, leaveDays: leaveDays, todayFocus: todayFocus, + liveHelpThisMonthSec: liveHelpThisMonthSec, ); final showSheetStats = diff --git a/lib/screens/programmer_tasks/programmer_tasks_list_screen.dart b/lib/screens/programmer_tasks/programmer_tasks_list_screen.dart index 82950f68..0290393e 100644 --- a/lib/screens/programmer_tasks/programmer_tasks_list_screen.dart +++ b/lib/screens/programmer_tasks/programmer_tasks_list_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart' show listEquals; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -45,9 +46,7 @@ class _ProgrammerTasksListScreenState if (idx >= 0) initialIndex = idx; } - if (_tabsBuilt && - _tabs == tabs && - _tabController.length == tabs.length) { + if (_tabsBuilt && listEquals(_tabs, tabs)) { return; } diff --git a/lib/utils/programmer_stats.dart b/lib/utils/programmer_stats.dart index b6cda7d4..01b2bc20 100644 --- a/lib/utils/programmer_stats.dart +++ b/lib/utils/programmer_stats.dart @@ -41,6 +41,10 @@ DateTime _weekStart(DateTime dt) { } /// Compute all programmer statistics from raw data. +/// +/// [liveHelpThisMonthSec] — when provided (from a live work-log query), this +/// value is used directly for [ProgrammerStats.helpGivenThisMonth] instead of +/// the approved-snapshot sum, so the stat reflects pending work too. ProgrammerStats computeProgrammerStats({ required String userId, required DateTime now, @@ -48,6 +52,7 @@ ProgrammerStats computeProgrammerStats({ required List sheets, required Set leaveDays, required Duration todayFocus, + int? liveHelpThisMonthSec, }) { final today = _dateOnly(now); final thisWeekStart = _weekStart(today); @@ -121,7 +126,8 @@ ProgrammerStats computeProgrammerStats({ return ProgrammerStats( todayFocus: todayFocus, - helpGivenThisMonth: Duration(seconds: helpGivenThisMonthSec), + helpGivenThisMonth: Duration( + seconds: liveHelpThisMonthSec ?? helpGivenThisMonthSec), approvedTotal: Duration(seconds: approvedTotalSec), helpGivenTotal: Duration(seconds: helpGivenTotalSec), completedThisWeek: completedThisWeek,