Skip to content

feat: add therapist dashboard metrics (patients/sessions/therapies)#214

Open
nthsneha wants to merge 6 commits into
AOSSIE-Org:mainfrom
nthsneha:feat/therapist-dashboard-counts
Open

feat: add therapist dashboard metrics (patients/sessions/therapies)#214
nthsneha wants to merge 6 commits into
AOSSIE-Org:mainfrom
nthsneha:feat/therapist-dashboard-counts

Conversation

@nthsneha
Copy link
Copy Markdown

@nthsneha nthsneha commented Mar 28, 2026

📝 Description

Adds therapist dashboard summary metrics and backend support to display:

  • total patients assigned to current therapist
  • total sessions for current therapist
  • total distinct therapies delivered by current therapist

🔧 Changes Made

  • therapist/lib/repository/supabase_therapist_repository.dart
    • implemented getTotalPatients(), getTotalSessions(), getTotalTherapies() in Supabase repository.
  • therapist/lib/provider/therapist_provider.dart
    • added totalPatients, totalSessions, totalTherapies
    • added fetchTotals() to call the new repository methods and update state.
  • therapist/lib/presentation/home/home_screen.dart
    • updated init logic to call fetchTotals() + fetchTherapistSessions()
    • updated summary stats section to display real values via Consumer2<TherapistDataProvider, SessionProvider>
  • Replaced static demo numbers in dashboard with live values.

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.

Summary by CodeRabbit

  • New Features

    • Statistics dashboard on therapist home showing total patients, sessions, and therapies.
    • Automatic background sync of activity progress when leaving the daily activities screen.
  • Bug Fixes / Improvements

    • Shows a failure notification if background sync fails.
    • Safer back-navigation to ensure progress is saved before exit.
    • Loading indicator shown while dashboard totals load.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 28, 2026

Warning

Rate limit exceeded

@nthsneha has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 40 minutes and 52 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 40 minutes and 52 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fb6f638a-2b78-467a-9a9a-16d82ea824aa

📥 Commits

Reviewing files that changed from the base of the PR and between 2b470a1 and 3959f9f.

📒 Files selected for processing (3)
  • patient/lib/presentation/activities/daily_activities_screen.dart
  • therapist/lib/presentation/home/home_screen.dart
  • therapist/lib/provider/therapist_provider.dart
📝 Walkthrough

Walkthrough

Refactors patient daily-activities back navigation to run a background sync before popping and surface sync failure via SnackBar; adds TaskProvider sync status and background update method. Adds therapist totals fetching (patients, sessions, therapies) and a stats dashboard on the home screen, with Supabase-backed repository implementations.

Changes

Cohort / File(s) Summary
Patient UI & Sync
patient/lib/presentation/activities/daily_activities_screen.dart
Replaces immediate pop with a PopScope-controlled flow; app bar uses Navigator.maybePop. On unsuccessful background save, shows a red SnackBar. Adds import for ApiStatus.
Patient Provider
patient/lib/provider/task_provider.dart
Adds _syncStatus and syncStatus getter; changes updateActivityCompletion() signature to use internal state; calls updated method from setSelectedDate; adds updateActivityInBackground() to manage background sync lifecycle and prevent concurrent syncs.
Therapist Home UI
therapist/lib/presentation/home/home_screen.dart
On init calls therapistProvider.fetchTotals() and SessionProvider.fetchTherapistSessions(); removes patients list UI and inserts a Consumer-driven stats dashboard with three StatsCards and loading handling.
Therapist Provider
therapist/lib/provider/therapist_provider.dart
Adds totalPatients, totalSessions, totalTherapies getters and _isLoading handling; adds fetchTotals() which concurrently calls three repo methods and updates totals on successful results.
Therapist Repository
therapist/lib/repository/supabase_therapist_repository.dart
Implements getTotalPatients(), getTotalSessions(), getTotalTherapies() with Supabase queries scoped to current user; returns counts (patients/sessions and distinct therapy types) and returns ActionResultFailure on exceptions instead of throwing.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Screen as Daily Activities Screen
    participant PopScope
    participant TaskProvider
    participant Repo as Task Repo

    User->>Screen: press back / navigate away
    Screen->>PopScope: onWillPop
    PopScope->>TaskProvider: updateActivityInBackground()
    TaskProvider->>TaskProvider: set syncStatus = loading / notifyListeners
    TaskProvider->>Repo: updateActivityCompletion(_allTasks)
    Repo-->>TaskProvider: result (success/failure)
    TaskProvider->>TaskProvider: copy _apiStatus -> _syncStatus / notifyListeners
    TaskProvider-->>PopScope: completion
    PopScope->>Screen: allow pop (Navigator.maybePop)
    Screen->>User: show SnackBar if syncStatus == ApiStatus.failure
Loading
sequenceDiagram
    participant HomeScreen
    participant TherapistProvider
    participant SessionProvider
    participant Repo as Supabase Repo

    HomeScreen->>TherapistProvider: fetchTotals()
    TherapistProvider->>Repo: getTotalPatients()
    TherapistProvider->>Repo: getTotalSessions()
    TherapistProvider->>Repo: getTotalTherapies()
    Repo-->>TherapistProvider: counts
    TherapistProvider->>TherapistProvider: update totals / notifyListeners
    HomeScreen->>SessionProvider: fetchTherapistSessions()
    SessionProvider-->>HomeScreen: sessions loaded
    HomeScreen->>HomeScreen: render stats dashboard or loader
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mdmohsin7
  • jddeep

Poem

🐇 I hopped through code with tiny paws,

Saved tasks in background without a pause.
Totals bloom on the dashboard bright,
Sync or fail — I guarded the night.
A rabbit cheers for progress in sight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main feature addition: therapist dashboard metrics (patients/sessions/therapies counts). It directly reflects the primary objective of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
therapist/lib/repository/supabase_therapist_repository.dart (1)

134-152: Avoid fetching full row lists just to compute totals.

Line 134-Line 152 currently materializes all matching IDs and counts client-side. This does unnecessary data transfer for large therapist datasets; prefer server-side counting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@therapist/lib/repository/supabase_therapist_repository.dart` around lines 134
- 152, The current implementations (e.g., getTotalSessions and the
patient-counting block) fetch full ID lists and count them client-side; change
the Supabase query to request a server-side count instead (use the select call's
count/fetch options such as FetchOptions(count: CountOption.exact) or the
client’s equivalent) on _supabaseClient.from('session') / from('patient') so the
query returns the total via response.count, then return that integer in
ActionResultSuccess instead of response.length.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@patient/lib/presentation/activities/daily_activities_screen.dart`:
- Around line 60-74: The code currently calls
TaskProvider.updateActivityInBackground() in both the onPopInvokedWithResult
callback and the AppBar leading IconButton onPressed, causing duplicate
background sync; remove the updateActivityInBackground() call from the
IconButton onPressed and leave only Navigator.pop(context) there so the
onPopInvokedWithResult closure remains the single place that triggers
updateActivityInBackground(), referencing updateActivityInBackground(),
onPopInvokedWithResult, the leading IconButton onPressed, TaskProvider, and
Navigator.pop.

In `@patient/lib/provider/task_provider.dart`:
- Around line 82-92: Add a reentrancy guard to updateActivityInBackground to
prevent concurrent runs: introduce a private boolean flag (e.g.
_isUpdatingActivities) and at the top of updateActivityInBackground return
immediately if the flag is true; set the flag true before setting _syncStatus
and calling updateActivityCompletion(_allTasks) and reset it to false in a
finally block so it always clears even on error. This ensures overlapping calls
cannot race on _syncStatus/_apiStatus or trigger duplicate backend updates while
keeping the existing _syncStatus = _apiStatus assignment and notifyListeners
behavior intact.

In `@therapist/lib/presentation/home/home_screen.dart`:
- Around line 255-300: You added a new dynamic Consumer2 block that renders
StatsCard widgets but left the old static metrics block in place, causing
duplicate stat sections; remove the earlier static metrics Container (the
previous group of StatsCard widgets) so only the
Consumer2<TherapistDataProvider, SessionProvider> block renders the stats,
ensuring you keep the StatsCard usages inside Consumer2 and delete the redundant
static cards/widgets.

In `@therapist/lib/provider/therapist_provider.dart`:
- Around line 182-203: The fetchTotals() method can leave _isLoading true if an
exception occurs and uses unsafe casts; wrap the body that calls
_therapistRepository.getTotalPatients/getTotalSessions/getTotalTherapies in a
try/finally so _isLoading is always set to false and notifyListeners() is called
in finally, and replace the `as int` casts by safe type checks on the
ActionResultSuccess.data (e.g., `if (patientsResult is ActionResultSuccess &&
patientsResult.data is int) _totalPatients = patientsResult.data as int` or
assign a fallback like 0 when data is null/invalid) so assignments for
_totalPatients, _totalSessions, and _totalTherapies never throw.

In `@therapist/lib/repository/supabase_therapist_repository.dart`:
- Around line 169-171: The distinct therapy count computation currently treats
null therapy_type_id as a valid distinct value; update the logic that builds
count (where response is List) to filter out nulls before mapping to
therapy_type_id and calling toSet(), e.g., only include entries with a non-null
e['therapy_type_id'] so the resulting set and count exclude nulls; locate the
block using the local variables response and count in
supabase_therapist_repository.dart and apply the filter there.

---

Nitpick comments:
In `@therapist/lib/repository/supabase_therapist_repository.dart`:
- Around line 134-152: The current implementations (e.g., getTotalSessions and
the patient-counting block) fetch full ID lists and count them client-side;
change the Supabase query to request a server-side count instead (use the select
call's count/fetch options such as FetchOptions(count: CountOption.exact) or the
client’s equivalent) on _supabaseClient.from('session') / from('patient') so the
query returns the total via response.count, then return that integer in
ActionResultSuccess instead of response.length.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 44cb7378-a439-42b6-93e3-64a5e474ac5a

📥 Commits

Reviewing files that changed from the base of the PR and between 051a4f3 and bfbc064.

📒 Files selected for processing (5)
  • patient/lib/presentation/activities/daily_activities_screen.dart
  • patient/lib/provider/task_provider.dart
  • therapist/lib/presentation/home/home_screen.dart
  • therapist/lib/provider/therapist_provider.dart
  • therapist/lib/repository/supabase_therapist_repository.dart

Comment thread patient/lib/presentation/activities/daily_activities_screen.dart Outdated
Comment thread patient/lib/provider/task_provider.dart
Comment thread therapist/lib/presentation/home/home_screen.dart Outdated
Comment thread therapist/lib/provider/therapist_provider.dart
Comment thread therapist/lib/repository/supabase_therapist_repository.dart
@Varadraj75
Copy link
Copy Markdown
Contributor

Hey @nthsneha, went through the diff. The intent is right but there are several issues across all three commits that block this from merging cleanly.

What's working

  • Implementing the three UnimplementedError stubs is the correct fix and the structure follows the existing repository pattern
  • PopScope wrapping is the right approach for catching system back navigation
  • Selecting only id and therapy_type_id instead of select('*') is more efficient
  • Distinct count logic for therapies via .toSet().length is a reasonable approach

Blockers

  1. Double sync on back navigation - onPopInvokedWithResult calls updateActivityInBackground() when didPop is true, and the back button's onPressed also calls it directly before Navigator.pop. One back tap fires two concurrent backend writes. Remove the call from onPressed and rely solely on onPopInvokedWithResult:
onPressed: () => Navigator.pop(context),
  1. No concurrency guard in updateActivityInBackground() - if the user triggers back navigation multiple times quickly, overlapping calls race on _syncStatus and _apiStatus and issue duplicate writes. Add an in-flight guard:
Future<void> updateActivityInBackground() async {
  if (_allTasks.isEmpty || _syncStatus == ApiStatus.loading) return;
  _syncStatus = ApiStatus.loading;
  notifyListeners();
  await updateActivityCompletion(_allTasks);
  _syncStatus = _apiStatus;
  notifyListeners();
}
  1. Duplicate stats section in the UI - the new Consumer2 block is added below the existing static StatsCard block (lines 217-253) without removing the old one. The dashboard will render two separate stats rows. Remove the old hardcoded block entirely.

  2. fetchTotals() can leave _isLoading stuck at true - the three as int casts have no try/finally wrapping. If any cast throws on an unexpected payload, _isLoading never resets to false and the loading spinner hangs forever. Wrap in try/finally:

Future<void> fetchTotals() async {
  _isLoading = true;
  notifyListeners();
  try {
    final patientsResult = await _therapistRepository.getTotalPatients();
    if (patientsResult is ActionResultSuccess) {
      _totalPatients = (patientsResult.data as num?)?.toInt() ?? 0;
    }
    // same for sessions and therapies
  } finally {
    _isLoading = false;
    notifyListeners();
  }
}
  1. null therapy_type_id counted as a distinct therapy - .toSet() on the mapped values will include null as one element if any rows have a null therapy_type_id. Filter before counting:
final count = response is List
    ? response.map((e) => e['therapy_type_id']).whereType<String>().toSet().length
    : 0;
  1. Sequential fetches instead of parallel - fetchTotals() awaits each repository call one at a time. Use Future.wait so all three fire concurrently:
final results = await Future.wait([
  _therapistRepository.getTotalPatients(),
  _therapistRepository.getTotalSessions(),
  _therapistRepository.getTotalTherapies(),
]);
  1. Sessions count pulled from SessionProvider instead of the repository - totalSessions in TherapistDataProvider is populated from getTotalSessions(), but the Consumer2 in the UI reads sessionProvider.totalSessions for the Sessions card. This means the Sessions card uses a different data source than Patients and Therapies. Either read all three from TherapistDataProvider or make the source consistent.

Minor

  • This PR overlaps significantly with feat: implement dynamic dashboard statistics for therapist home screen #205 which implements the same three repository methods and the same fetchDashboardStats flow. Worth coordinating with the team on which one to take forward to avoid double-reviewing the same work
  • _syncStatus is exposed via syncStatus getter but nothing in the UI reads it, so the loading state from background sync is invisible to the user

Fix the double-sync, the stuck loading state, the duplicate UI block, and the null therapy count and this is close to mergeable.

@nthsneha
Copy link
Copy Markdown
Author

@Varadraj75 Made the changes

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@patient/lib/presentation/activities/daily_activities_screen.dart`:
- Around line 59-75: The callback passed to PopScope uses
context.read<TaskProvider>() after awaiting updateActivityInBackground(), which
is unsafe if the widget is disposed; capture the provider before the async gap
by obtaining a local variable (e.g., final taskProvider =
context.read<TaskProvider>() or final provider = context.read<TaskProvider>())
prior to calling await taskProvider.updateActivityInBackground(), then use that
local reference to check provider.syncStatus and avoid using
context.read<TaskProvider>() after the await while still respecting mounted
before calling Navigator.pop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9860b446-ad6e-446e-bfcd-e6b8c62791f3

📥 Commits

Reviewing files that changed from the base of the PR and between bfbc064 and d168377.

📒 Files selected for processing (5)
  • patient/lib/presentation/activities/daily_activities_screen.dart
  • patient/lib/provider/task_provider.dart
  • therapist/lib/presentation/home/home_screen.dart
  • therapist/lib/provider/therapist_provider.dart
  • therapist/lib/repository/supabase_therapist_repository.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • therapist/lib/presentation/home/home_screen.dart
  • therapist/lib/repository/supabase_therapist_repository.dart
  • therapist/lib/provider/therapist_provider.dart

Comment thread patient/lib/presentation/activities/daily_activities_screen.dart
@Varadraj75
Copy link
Copy Markdown
Contributor

@nthsneha, went through the latest diff. Two blockers still need fixing before this merges, plus one thing worth verifying.


Blocker 1: context.read after await in daily_activities_screen.dart

This is the same issue flagged in #213. The provider is captured after the await, not before:

await context.read<TaskProvider>().updateActivityInBackground(); // fine
final taskProvider = context.read<TaskProvider>(); // unsafe, this is after the async gap

If the widget unmounts during the network call, the second context.read will crash. Capture the provider once before the await and reuse it:

final taskProvider = context.read<TaskProvider>();
await taskProvider.updateActivityInBackground();
if (taskProvider.syncStatus == ApiStatus.failure && mounted) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Failed to save your progress'),
      backgroundColor: Colors.red,
      duration: Duration(seconds: 2),
    ),
  );
}
if (mounted) Navigator.pop(context);

Blocker 2: Shared _isLoading flag races between concurrent fetches

In initState, fetchPatientsMappedToTherapist() and fetchTotals() are called without awaiting, so they run concurrently. Both write to the same _isLoading field. Whichever finishes first sets _isLoading = false and calls notifyListeners(), which collapses the loading spinner while the other call is still in flight. The UI will flicker or render stale data mid-load.

These two methods need separate loading flags:

bool _isTotalsLoading = false;
bool _isPatientsLoading = false;

Each method manages only its own flag, and the UI can derive the combined loading state from both if needed.


Worth Verifying: SessionProvider fetch dropped

The old initState had this call:

Provider.of<SessionProvider>(context, listen: false).fetchTherapistSessions();

This is gone in the new code. If anything else in this screen tree reads from SessionProvider, those widgets will show stale or empty data. Please confirm this removal is intentional.


What looks good

  • Future.wait for parallel repository calls in fetchTotals() is correct
  • try/finally in fetchTotals() ensures the loading flag never gets stuck
  • Null therapy_type_id values are filtered before the distinct count
  • Old hardcoded stats block is fully removed, no duplicate UI section
  • totalSessions now reads from TherapistDataProvider consistently, not from a separate SessionProvider

Fix the two blockers and confirm the SessionProvider drop and this is ready to merge.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@patient/lib/presentation/activities/daily_activities_screen.dart`:
- Around line 61-74: The onPopInvokedWithResult callback currently awaits
taskProvider.updateActivityInBackground() and then calls Navigator.pop, but if
updateActivityInBackground() throws the pop will never run; wrap the async call
in a try { await taskProvider.updateActivityInBackground(); } catch (e) { /*
optionally log or handle */ } finally { if (taskProvider.syncStatus ==
ApiStatus.failure && mounted) show the SnackBar; if (mounted)
Navigator.pop(context); } so that updateActivityInBackground (referenced by
TaskProvider.updateActivityInBackground) errors are handled and
Navigator.pop(context) always executes from the finally block while preserving
the existing snackbar logic that checks taskProvider.syncStatus and mounted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2661bb83-95a8-4007-92c7-fa2ed4b5bebe

📥 Commits

Reviewing files that changed from the base of the PR and between d168377 and 2b470a1.

📒 Files selected for processing (1)
  • patient/lib/presentation/activities/daily_activities_screen.dart

Comment thread patient/lib/presentation/activities/daily_activities_screen.dart Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants