Skip to main content

Activity Dashboard — Member Productivity Tracker

1. Overview

The Activity Dashboard is a Zoho Creator application that tracks user activity and reports member productivity.

Every user action inside the app (open file, add note, add demand, add task, etc.) is recorded in the Activity_Log form with a timestamp. A background scheduler runs at the end of each day, calculates productive/break/idle minutes per user, and saves the result to Daily_Activity_Summary. The dashboard page reads from Daily_Activity_Summary and displays member productivity filtered by team and date range.

2. How It Works

Key forms:

FormPurpose
Activity_LogStores every user action with action type and timestamp
Daily_Activity_SummaryStores the daily calculated summary per user
TeamStores team members and leaders

Data flow:

  1. A user performs an action (opens a file, adds a note, submits a form, etc.) → an entry is written to Activity_Log.
  2. At the end of the day, the Daily User Activity Monitoring scheduler batches unprocessed Activity_Log records and triggers the Migrate Daily Activity Summary scheduler.
  3. That scheduler calculates productive/break/idle minutes and per-action-type counts, and writes/replaces a Daily_Activity_Summary record for each user for that day, then marks the source logs as Migration = true.
  4. The dashboard page calls get_dashboard_html_data(), which aggregates Daily_Activity_Summary records for the selected team and date range and renders the HTML.

Action types tracked (recorded via action_type on Activity_Log, counted per day on Daily_Activity_Summary):

Action TypeLogged When
open_fileUser visits the claim detail page, on web (Claim_dhtml) or mobile (Claim_mhtml)
update_claimUser updates the Claim form
update_cc_assistUser updates the Update Fields CC Assist form
add_noteUser adds a note via the Add Note form
add_file_summaryUser creates a File Summary
update_file_summaryUser updates an existing File Summary
update_claims_mgmtUser updates the Claims Management form
add_parts_orderUser creates a new Parts Order
update_parts_orderUser updates an existing Parts Order
update_auditUser updates the Assessment Audit form
add_demandUser creates a new Demand via the Add Demand form
update_demandUser updates an existing Demand
add_taskUser creates a new Task
update_law_firmUser updates the Law Firm form
total_eventsAggregate of all action types combined

3. Time Classification

Activity time per user is bucketed into three states, all stored in minutes and displayed in Xh Ym format. These are calculated per day by fn_calc_productive_hours from the raw Activity_Log timestamps for that user/day:

StateDefinition
ProductiveSum of the gaps between consecutive Activity_Log events on that day, counted only where the gap is ≤ IDLE_THRESHOLD (30 minutes)
BreakCurrently a fixed 30 minutes/day placeholder — a real calculation against a Break_Log form exists in the code but is commented out pending that form being finished
IdleThe window from the day's first event to its last event, minus productive and break minutes, floored at 0

Note: a gap between events that is longer than 30 minutes is not added to productive time, but it also isn't separately bucketed — it simply falls out of the productive sum and is absorbed into the idle calculation below.

Productive hour vs. non-productive hour:

  • Productive hour — time where the user's next Activity_Log event follows within 30 minutes of the previous one. The system treats a fast follow-up action as evidence the user was continuously working during that gap, so the whole gap (not just the endpoints) is counted as productive.
  • Non-productive hour — everything else in the user's tracked day: break (the fixed 30-min/day placeholder) + idle (any gap longer than 30 minutes between actions, i.e. the user stepped away or stopped working). On the dashboard's time bar, the yellow (break) and grey (idle) segments together represent non-productive time, versus the green (productive) segment.
  • A user with very few Activity_Log events in a day — even if spread evenly — will show mostly idle/non-productive time, because idle is derived from the whole first-to-last-event window, not just the gaps between logged actions.

4. Daily Summary Calculation

Handled by two schedulers defined in user-activity-dashboard.deluge:

Daily User Activity Monitoring (runs once per day)

  • Reads unprocessed logs: Activity_Log[Migration == false].
  • If there are fewer than 200 unprocessed logs, schedules a single Migrate_Daily_Activity_Summary run 15 minutes later.
  • If there are 200+ logs, splits the backlog into batches of 200 and schedules up to 30 staggered Migrate_Daily_Activity_Summary runs (one per batch, a few minutes apart).
  • Re-schedules itself to run again +1 day.

Migrate Daily Activity Summary (per batch)

  • Pulls up to 200 unmigrated logs: Activity_Log[Migration == false] range from 0 to 200.
  • For each distinct user/day combination:
    • Deletes any existing Daily_Activity_Summary record for that user/day (to avoid duplicates on re-run).
    • Calls fn_calc_productive_hours(user, date) and fn_count_events(user, date).
    • Inserts a new Daily_Activity_Summary record and populates it with the returned productive/break/idle minutes and per-action-type counts.
    • Marks each processed Activity_Log record as Migration = true.
stats = thisapp.activity_log.fn_calc_productive_hours(log.User,log_date.toDate());
counts = thisapp.activity_log.fn_count_events(log.User,log_date.toDate());

fn_calc_productive_hours(user_id, day_date)

  • Pulls that user's Activity_Log rows for the day, sorted by logged_at ascending.
  • Walks consecutive pairs of events: if the gap between them is ≤ IDLE_THRESHOLD (30 min), the gap is added to productive_mins.
  • break_mins is hardcoded to 30 (a Break_Log-based calculation is written but commented out until that form is ready).
  • idle_mins = (time from first event to last event) − productive_minsbreak_mins, clamped to a minimum of 0.
  • Returns a map: productive_mins/productive_hours, break_mins/break_hours, idle_mins/idle_hours (hours are minutes ÷ 60, rounded to 1 decimal).
{"productive_mins":152,"productive_hours":2.5,"break_mins":30,"break_hours":0.5,"idle_mins":88,"idle_hours":1.5}

fn_count_events(user_id, day_date)

  • Pulls that user's Activity_Log rows for the day (no sorting needed).
  • Tallies occurrences per Action_Type into a map (e.g. open_file, add_note, update_demand, …).
  • Adds a total_events key holding the sum of all per-action-type counts.
  • Returns the counts map directly — this is what Migrate Daily Activity Summary reads via counts.containKey(...) / counts.get(...) to populate each Daily_Activity_Summary field.

5. Dashboard Features

Activity Dashboard — team summary and member cards

  • Team and date range filter — selects the team (input.team_id) and date window (input.from_date / input.to_date); defaults to the first team alphabetically if none is selected.

  • Summary cards — Active Members (active/total), Total Events (across all members), Avg Productive time (per member).

  • Member cards — one card per team member/leader:

    • Avatar showing initials, member name, and (for team leaders) a Leader badge.
    • Active (green) / Inactive (red) status badge, driven by the linked User.Status field.
    • A productive/break/idle time bar with a dot-legend underneath (● productive, ● break, ● idle), each labelled in Xh Ym format — e.g. 10h 46m productive, 3h 30m break, 59h 0m idle.
    • Four action-count tiles: Files, Notes, Demands, Tasks.
  • Last Summary Date — shown at the top of the dashboard via get_last_summary_date(), indicating data freshness (e.g. LAST SUMMARY DATE: 24-JUL-2026).

  • Multi-day range support — when a date range spans multiple days, build_member_data() sums all matching Daily_Activity_Summary rows per user across the range rather than showing a single day.

  • All Activities Graph — a separate report widget (not part of the HTML snippet above) showing a stacked bar chart of every action-type total per user, filterable by User and Summary Date, with a toggleable legend covering all tracked action types (all_action_flows, update_cc_assist, update_claim, update_audit, add_note, update_parts_order, update_demand, add_file_summary, update_claims_mgmt, update_file_summary, update_law_firm, add_demand, add_task):

    All Activities Graph — stacked action-type totals per user

  • Tab/table view of raw activity logs — referenced in the HTML (tab bar, per-tab table, get_tab_activity_logs()) but currently commented out / disabled in the shipped markup.

6. Deluge Functions

FunctionSignaturePurpose
get_dashboard_html_data(team_id, from_date, to_date)Builds the full dashboard payload for a team: team name, member list, totals, and average productive time
build_member_data(user_id, is_leader, from_date, to_date)Aggregates one user's Daily_Activity_Summary rows across the date range into a single display-ready map
get_last_summary_date()Returns the most recent date a daily summary was calculated, for the freshness indicator
get_tab_activity_logs(team_id, action_type, from_date, to_date)Returns raw activity log rows for a given action type tab (currently unused — tabs are disabled)
fn_calc_productive_hours(user_id, date)Calculates productive minutes from consecutive Activity_Log gaps (≤30 min), a fixed 30-min break placeholder, and idle as the remainder — see Time Classification
fn_count_events(user_id, date)Counts occurrences of each Action_Type for a user on a given day, plus a total_events sum

7. Key Logic

Team aggregation (get_dashboard_html_data):

  • Iterates Team.Team_Leaders and Team.Team_Members separately, calling build_member_data() for each and tagging leaders with is_leader = true.
  • Skips any member with no Daily_Activity_Summary records in range (build_member_data returns an empty map, which is not added to the list).
  • Computes avg_productive as total productive minutes across all included members divided by member count, rounded to 1 decimal.

Per-member aggregation (build_member_data):

  • Sums productive_mins, break_mins, idle_mins, and all per-action-type counters across every Daily_Activity_Summary row in the date range for that user.
  • Derives initials from the user's name by taking the first letter of each space-separated part (e.g. "Vignesh Driver" → VD, "Demo User 3" → DU3).
  • Formats minutes into Xh Ym strings for productive/break/idle time.
  • Flags is_active based on the linked User.Status field — drives the green Active / red Inactive badge on the member card.
  • Only open_file, add_note, update_demand, and add_task counts surface on the member card tiles (Files / Notes / Demands / Tasks); the remaining action-type counters (update_claim, update_cc_assist, add_file_summary, update_file_summary, update_claims_mgmt, add_parts_order, update_parts_order, update_audit, update_law_firm) are aggregated but only exposed via the All Activities Graph, not the member card.

Time bar rendering (dashboard HTML):

  • Converts each member's productive/break/idle minutes into percentages of their total tracked time (guarding against a zero-minute total by treating it as 1) to size the three segments of the time bar.
total_mins = prod_mins + brk_mins + idle_mins;
if(total_mins <= 0)
{
total_mins = 1;
}
p_pct = ((prod_mins * 100.0) / total_mins).round(1);