feat(dashboard): specialized dashboard — actions, role gating, clickable links#626
feat(dashboard): specialized dashboard — actions, role gating, clickable links#626arifulhoque7 wants to merge 8 commits into
Conversation
Add a new React dashboard (landing page) backed by a single role-scoped REST endpoint, plus a WP-admin "Dashboard" submenu link. Backend (pm/v2/dashboard, pm/v2/dashboard/heatmap): - Aggregates: KPIs, project status, task performance (7/30d), distribution, upcoming tasks, overdue/priority, milestones, recent activity, team, and a GitHub-style productivity heatmap with a year filter. - Three scope tiers: admin -> full org, manager -> their projects, member -> own assigned tasks. All figures read live from pm_* tables (no mock data). Frontend (views/assets/src/components/dashboard): - shadcn/ui + Recharts; brand-accent theme; Mona Sans font (matches WP ERP). - Local shadcn Calendar wrapper (react-day-picker v9) for the mini calendar. - Every card lazy-loaded behind its own Suspense boundary. - New /dashboard route is the landing page; sidebar + WP submenu link added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pm-accent was 'var(--pm-accent)' (a hex), so utilities like bg-pm-accent/10 emitted an invalid color and rendered nothing (e.g. avatar fallbacks in Activities/Progress). Add --pm-accent-hsl channels and define the token as hsl(var(--pm-accent-hsl) / <alpha-value>) so opacity modifiers work app-wide. Solid bg-pm-accent and direct var(--pm-accent) CSS uses are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New Task button opens NewTaskSheet inline (was navigating to /my-tasks); refetches dashboard on create - New Project button opens the shared ProjectCreateSheet via Redux and preloads categories/roles so dropdowns aren't empty when opened cold - Per-button role gating: New Task (any user), New Project (canCreate), Milestone (manager) — matches the rest of the app - Active Projects rows now open /task-lists to match the projects list - Recent Activity actor + project are clickable: emit project_id/task_id from the controller and deep-link to the task / project task list Refs weDevsOfficial/pm-pro#357
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
WalkthroughIntroduces a PM dashboard with a scoped REST backend, a new ChangesDashboard Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Stylelint (17.13.0)views/assets/src/tailwind.cssError: ENOENT: no such file or directory, open '/.stylelintrc.json' 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@routes/dashboard.php`:
- Around line 8-13: Both the 'dashboard' and 'dashboard/heatmap' routes in the
router configuration are missing the required validator and sanitizer method
chains. Add ->validator() and ->sanitizer() method calls to each route
definition, chaining them after the existing ->permission() method. These
methods are mandatory for all REST endpoints in the codebase to ensure proper
input validation and sanitization of request data.
In `@src/Dashboard/Controllers/Dashboard_Controller.php`:
- Around line 64-82: The Dashboard_Controller is returning raw associative
arrays directly via rest_ensure_response instead of using Fractal transformers
for serialization. Replace the raw array construction and response return in
this method with proper Fractal transformer implementation. Create or use an
existing Fractal transformer class to serialize the dashboard data array that
includes user, range, kpis, projects_status, performance, task_distribution,
upcoming, overdue_list, calendar, active_projects, recent_activity, milestones,
team, and generated_at fields, then pass the transformed data through Fractal
before returning the response to ensure compliance with the project's API
serialization standards.
- Line 163: The task status aggregation in the Dashboard_Controller where clause
is using the Task::PENDING constant, but task status is defined as a 2-state
integer model with values 0 or 1. Replace the Task::PENDING constant in the
where clause with the correct integer value (0 or 1) that represents pending
status according to the task status contract, ensuring consistency between the
aggregated KPI data and persisted task values. Apply the same fix to line 382
where this pattern also occurs.
- Around line 209-212: The integer casting (int) on $p->status is incorrect
because project status is a STRING value, not an integer, and casting will
misclassify statuses and break the counter logic. Remove the (int) casting from
both comparisons in the if and elseif conditions that check against
Project::COMPLETE and Project::ARCHIVED constants, comparing the status directly
as a string instead.
In `@views/assets/src/components/dashboard/DashboardPage.jsx`:
- Around line 57-77: The load function in DashboardPage has a race condition
where rapid range changes can cause stale responses to overwrite newer data. Fix
this by implementing request tracking: create a ref to store the current request
ID or timestamp, increment it at the start of the load function, and only update
setData and setError if the current request ID matches the stored one when the
API response returns. Alternatively, use an AbortController to cancel the
previous api.get request when a new load call is made, ensuring only the latest
request completes and updates the state.
In `@views/assets/src/components/dashboard/parts/StatCard.jsx`:
- Line 43: The ternary operator on line 43 in the StatCard component currently
defaults all non-down trend directions to emerald-500 (positive color), which
incorrectly treats flat trends as positive. Update the conditional logic to
explicitly check for trend.direction === 'flat' and apply a neutral/muted color
(such as text-slate-500 or text-gray-500), then check for 'down' to apply
rose-500, and finally default to text-emerald-500 for positive trends. This
ensures flat trends are visually represented with a neutral tone separate from
positive and negative indicators.
- Around line 13-18: The StatCard component uses a non-interactive `<div>`
element with an onClick handler, which lacks keyboard accessibility and prevents
non-pointer users from navigating KPIs. Replace the `<div>` element that wraps
the card content with a `<button>` element, which will inherently provide
keyboard accessibility and proper semantic meaning for clickable content. Ensure
the button still applies the same className and onClick handler as before to
maintain styling and functionality.
In `@views/assets/src/tailwind.css`:
- Around line 514-516: The z-index values in the tailwind.css file exceed the
project's maximum allowed z-index of 700 for plugin styles. Update the z-index
property in the [data-radix-popper-content-wrapper] selector from 3000 to 700,
and also reduce the z-index values at the locations around lines 551 and 580 to
comply with the 700 maximum limit. Ensure all three z-index declarations (3000,
2001, and 2501) are replaced with values that do not exceed 700 to prevent
breaking expected layering behavior.
- Line 2: The `@import` statement in tailwind.css uses the url() notation which
does not comply with the configured Stylelint rules. Remove the url() wrapper
from the Google Fonts import statement for Mona Sans and use plain string
notation instead, keeping the quoted URL string directly in the `@import`
declaration to satisfy Stylelint requirements.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9918b550-13c6-41ac-889a-5fcdb155157d
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (26)
core/WP/Menu.phppackage.jsonroutes/dashboard.phpsrc/Dashboard/Controllers/Dashboard_Controller.phptailwind.config.jsviews/assets/src/components/dashboard/DashboardPage.jsxviews/assets/src/components/dashboard/parts/ActiveProjectsCard.jsxviews/assets/src/components/dashboard/parts/DashboardHeader.jsxviews/assets/src/components/dashboard/parts/KpiCards.jsxviews/assets/src/components/dashboard/parts/MilestonesCard.jsxviews/assets/src/components/dashboard/parts/MiniCalendarCard.jsxviews/assets/src/components/dashboard/parts/OverduePriorityCard.jsxviews/assets/src/components/dashboard/parts/ProInsightsRow.jsxviews/assets/src/components/dashboard/parts/ProUpgradeCard.jsxviews/assets/src/components/dashboard/parts/ProductivityHeatmapCard.jsxviews/assets/src/components/dashboard/parts/ProjectStatusCard.jsxviews/assets/src/components/dashboard/parts/RecentActivityCard.jsxviews/assets/src/components/dashboard/parts/StatCard.jsxviews/assets/src/components/dashboard/parts/TaskDistributionCard.jsxviews/assets/src/components/dashboard/parts/TaskPerformanceCard.jsxviews/assets/src/components/dashboard/parts/TeamStatusCard.jsxviews/assets/src/components/dashboard/parts/UpcomingScheduleCard.jsxviews/assets/src/components/layout/AppSidebar.jsxviews/assets/src/components/ui/calendar.jsxviews/assets/src/index.jsxviews/assets/src/tailwind.css
| $wedevs_pm_router->get( 'dashboard', 'WeDevs/PM/Dashboard/Controllers/Dashboard_Controller@index' ) | ||
| ->permission( ['WeDevs\PM\Core\Permissions\Authentic'] ); | ||
|
|
||
| // GET /wp-json/pm/v2/dashboard/heatmap?year=YYYY — productivity heatmap (self-fetched by the card). | ||
| $wedevs_pm_router->get( 'dashboard/heatmap', 'WeDevs/PM/Dashboard/Controllers/Dashboard_Controller@heatmap_data' ) | ||
| ->permission( ['WeDevs\PM\Core\Permissions\Authentic'] ); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Add validator and sanitizer to both dashboard endpoints.
Both routes are missing ->validator() and ->sanitizer(), which are mandatory for all REST endpoints in this codebase.
🔧 Proposed fix
$wedevs_pm_router->get( 'dashboard', 'WeDevs/PM/Dashboard/Controllers/Dashboard_Controller@index' )
- ->permission( ['WeDevs\PM\Core\Permissions\Authentic'] );
+ ->permission( ['WeDevs\PM\Core\Permissions\Authentic'] )
+ ->validator( ['WeDevs\PM\Dashboard\Validators\Dashboard_Validator'] )
+ ->sanitizer( ['WeDevs\PM\Dashboard\Sanitizers\Dashboard_Sanitizer'] );
$wedevs_pm_router->get( 'dashboard/heatmap', 'WeDevs/PM/Dashboard/Controllers/Dashboard_Controller@heatmap_data' )
- ->permission( ['WeDevs\PM\Core\Permissions\Authentic'] );
+ ->permission( ['WeDevs\PM\Core\Permissions\Authentic'] )
+ ->validator( ['WeDevs\PM\Dashboard\Validators\Dashboard_Heatmap_Validator'] )
+ ->sanitizer( ['WeDevs\PM\Dashboard\Sanitizers\Dashboard_Heatmap_Sanitizer'] );As per coding guidelines, “Every REST endpoint must have a validator via ->validator() and sanitizer via ->sanitizer() methods.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@routes/dashboard.php` around lines 8 - 13, Both the 'dashboard' and
'dashboard/heatmap' routes in the router configuration are missing the required
validator and sanitizer method chains. Add ->validator() and ->sanitizer()
method calls to each route definition, chaining them after the existing
->permission() method. These methods are mandatory for all REST endpoints in the
codebase to ensure proper input validation and sanitization of request data.
Source: Coding guidelines
| $data = [ | ||
| 'user' => $this->user_block(), | ||
| 'range' => $days, | ||
| 'kpis' => $this->kpis(), | ||
| 'projects_status' => $this->projects_status(), | ||
| 'performance' => $this->performance( $days ), | ||
| 'task_distribution' => $this->task_distribution(), | ||
| 'upcoming' => $this->upcoming_tasks(), | ||
| 'overdue_list' => $this->overdue_tasks(), | ||
| 'calendar' => $this->calendar_month(), | ||
| 'active_projects' => $this->active_projects(), | ||
| 'recent_activity' => $this->recent_activity(), | ||
| 'milestones' => $this->upcoming_milestones(), | ||
| 'team' => ( $this->is_admin || $this->is_manager ) ? $this->team_status() : [], | ||
| 'generated_at' => current_time( 'mysql' ), | ||
| ]; | ||
|
|
||
| return rest_ensure_response( [ 'data' => $data ] ); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use Fractal transformers for dashboard responses instead of raw arrays.
The controller is returning raw associative arrays directly; this bypasses the project’s required Fractal serialization contract for src/** API output.
As per coding guidelines, “Use Fractal transformers for API response serialization.”
Also applies to: 303-303
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Dashboard/Controllers/Dashboard_Controller.php` around lines 64 - 82, The
Dashboard_Controller is returning raw associative arrays directly via
rest_ensure_response instead of using Fractal transformers for serialization.
Replace the raw array construction and response return in this method with
proper Fractal transformer implementation. Create or use an existing Fractal
transformer class to serialize the dashboard data array that includes user,
range, kpis, projects_status, performance, task_distribution, upcoming,
overdue_list, calendar, active_projects, recent_activity, milestones, team, and
generated_at fields, then pass the transformed data through Fractal before
returning the response to ensure compliance with the project's API serialization
standards.
Source: Coding guidelines
| $total = (clone $this->task_query())->count(); | ||
| $completed = (clone $this->task_query())->where( 'status', Task::COMPLETE )->count(); | ||
| $in_progress = (clone $this->task_query())->where( 'status', Task::INCOMPLETE )->count(); | ||
| $pending = (clone $this->task_query())->where( 'status', Task::PENDING )->count(); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Align task-status aggregation with the 0/1 task status contract.
pending is being counted via Task::PENDING, but task status is defined as a 2-state integer model. This can make KPI/distribution data inconsistent with persisted values.
As per coding guidelines, “Task status is an INTEGER (0 or 1), NOT a boolean.”
Also applies to: 382-382
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Dashboard/Controllers/Dashboard_Controller.php` at line 163, The task
status aggregation in the Dashboard_Controller where clause is using the
Task::PENDING constant, but task status is defined as a 2-state integer model
with values 0 or 1. Replace the Task::PENDING constant in the where clause with
the correct integer value (0 or 1) that represents pending status according to
the task status contract, ensuring consistency between the aggregated KPI data
and persisted task values. Apply the same fix to line 382 where this pattern
also occurs.
Source: Coding guidelines
| if ( (int) $p->status === Project::COMPLETE ) { | ||
| $completed++; | ||
| } elseif ( (int) $p->status === Project::ARCHIVED ) { | ||
| $archived++; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Remove integer casting from project status checks.
Casting project status to (int) can misclassify string statuses and break the on-track/completed/archived counters.
🔧 Proposed fix
- foreach ( $projects as $p ) {
- if ( (int) $p->status === Project::COMPLETE ) {
+ foreach ( $projects as $p ) {
+ if ( $p->status === 'complete' ) {
$completed++;
- } elseif ( (int) $p->status === Project::ARCHIVED ) {
+ } elseif ( $p->status === 'archived' ) {
$archived++;
} elseif ( $p->overdue_count > 0 ) {
$at_risk++;
} else {
$on_track++;
}
}As per coding guidelines, “Project status is a STRING ('incomplete' | 'complete' | 'archived' | 'favourite'), NOT an enum or integer.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/Dashboard/Controllers/Dashboard_Controller.php` around lines 209 - 212,
The integer casting (int) on $p->status is incorrect because project status is a
STRING value, not an integer, and casting will misclassify statuses and break
the counter logic. Remove the (int) casting from both comparisons in the if and
elseif conditions that check against Project::COMPLETE and Project::ARCHIVED
constants, comparing the status directly as a string instead.
Source: Coding guidelines
| [data-radix-popper-content-wrapper] { | ||
| z-index: 3000 !important; | ||
| font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; | ||
| font-family: 'Mona Sans', Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Reduce dialog/popper z-index to the project maximum.
Line 515 (3000), Line 551 (2001), and Line 580 (2501) exceed the allowed plugin max z-index and can break expected layering behavior.
Suggested fix
[data-radix-popper-content-wrapper] {
- z-index: 3000 !important;
+ z-index: 700 !important;
font-family: 'Mona Sans', Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
}
[role="dialog"][data-state] {
- z-index: 2001 !important;
+ z-index: 700 !important;
font-family: 'Mona Sans', Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
[role="alertdialog"][data-state] {
- z-index: 2501 !important;
+ z-index: 700 !important;
font-family: 'Mona Sans', Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}Also applies to: 550-552, 579-581
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@views/assets/src/tailwind.css` around lines 514 - 516, The z-index values in
the tailwind.css file exceed the project's maximum allowed z-index of 700 for
plugin styles. Update the z-index property in the
[data-radix-popper-content-wrapper] selector from 3000 to 700, and also reduce
the z-index values at the locations around lines 551 and 580 to comply with the
700 maximum limit. Ensure all three z-index declarations (3000, 2001, and 2501)
are replaced with values that do not exceed 700 to prevent breaking expected
layering behavior.
Source: Coding guidelines
PR weDevsOfficial#626 review fixes (safe subset): - DashboardPage: guard load() with requestSeqRef so stale range responses cannot overwrite newer data - StatCard: render clickable card as <button> for keyboard a11y; color 'flat' trend neutral instead of positive emerald - tailwind.css: drop url() from @import for Stylelint import-notation
Summary
Wires up the specialized dashboard header actions, role gating, and clickable links.
NewTaskSheetinline (was navigating to/my-tasks); refetches dashboard on createProjectCreateSheetvia Redux; preloads categories/roles so dropdowns aren't empty when opened coldcanCreate), Milestone (manager) — matches the rest of the app/task-liststo match the projects listproject_id/task_id, frontend deep-links to the task / project task listTest
pnpm build✓Refs weDevsOfficial/pm-pro#357
Summary by CodeRabbit
Release Notes
New Features
/dashboardwith personalized KPI tiles, performance trends, project status, active projects, mini calendar, productivity heatmap, recent activity, upcoming milestones, overdue prioritization, and (for Pro) team workload visibility.Style