Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Aganet analytics#93

Open
AdithaBuwaneka wants to merge 4 commits intoDevfrom
aganet--analytics
Open

Aganet analytics#93
AdithaBuwaneka wants to merge 4 commits intoDevfrom
aganet--analytics

Conversation

@AdithaBuwaneka
Copy link
Copy Markdown
Member

@AdithaBuwaneka AdithaBuwaneka commented Aug 16, 2025

Pull Request

Summary

  • What does this PR change and why?
  • Any relevant context or background?

Area of change

  • Frontend
  • Backend
  • Database
  • DevOps
  • Documentation
  • Other (describe below)

Type of change

  • Feature
  • Bug fix
  • Refactor
  • Performance
  • Accessibility
  • Documentation
  • CI/CD / Chore
  • Other (describe below)

If Other, describe:

Screenshots or recordings (UI changes)

  • Add before/after images or a short clip. Note light/dark mode where relevant, if applicable.

How to test

  • Prereqs: Node LTS
  • Steps to validate locally:
    1. npm ci
    2. npm run lint
    3. npm run build
    4. npm start
    5. Navigate to affected pages and verify behavior
  • Include any mock data, flags, or URLs.

Acceptance criteria checklist

  • Lint passes locally (npm run lint)
  • Production build succeeds (npm run build)
  • No console errors or TypeScript errors in dev
  • UX reviewed (if UI changes)
  • i18n/text reviewed (if user-facing text)
  • Docs updated where needed (README.md / DEPLOYMENT.md)

Accessibility checklist (if UI)

  • Keyboard navigation and focus order make sense
  • Sufficient color contrast
  • ARIA roles/labels where appropriate
  • Images/icons have accessible names or alt text

Security & privacy

  • No secrets committed; uses environment variables
  • No introduction of new PII collection/storage
  • Input validated/sanitized where applicable
  • Third-party deps reviewed (scope minimal)

Breaking changes

  • Yes (describe migration/rollback)
  • No

If breaking, describe migration steps and rollback plan:

Deployment notes

  • Any special steps or config? Reference DEPLOYMENT.md if needed.
  • Service/restart impacts? (see deploy/govlink.service and scripts)

Additional notes

  • Risks, follow-ups, or related issues/PRs.

Summary by CodeRabbit

  • New Features
    • Agent Analytics: Added live dashboards with quick stats, performance charts, skills, trends, peak hours, and resource usage.
    • Reports: Generate or schedule reports and view recent history with real data.
    • Performance View: Personal metrics, team ranking, and chart data with time range and view toggles.
    • Dashboard: Agent dashboard now shows live stats and auto-refreshes.
    • Simplified Analytics: New streamlined analytics view replaces the previous dashboard on the analytics page.
    • Loading States: Added spinners and graceful empty states across analytics and reports experiences.

…rehensive data handling and performance metrics

- Added GET endpoint for agent analytics to fetch appointment statistics and quick stats.
- Introduced GET endpoint for agent trends analysis, including traffic, response time, and service type trends.
- Created GET endpoint for agent dashboard to provide real-time statistics on appointments and submissions.
- Developed SimplifiedAnalytics component to display analytics data, performance metrics, and trends in a user-friendly interface.
- Enhanced data fetching logic with error handling and loading states for better user experience.
Copilot AI review requested due to automatic review settings August 16, 2025 17:42
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Aug 16, 2025

Walkthrough

Introduces multiple agent analytics API routes (analytics, performance, trends, reports) and wires UI components to fetch real data. Adds agent dashboard API and connects StatsOverview. Adds auth helpers for agent/admin. Minor adjustments to admin management, debug route signatures, and user pages. Replaces AnalyticsPage content with SimplifiedAnalytics.

Changes

Cohort / File(s) Summary of changes
Agent Analytics API Routes
src/app/api/agent/analytics/route.ts, src/app/api/agent/analytics/performance/route.ts, src/app/api/agent/analytics/trends/route.ts, src/app/api/agent/analytics/reports/route.ts
Added GET endpoints for overall analytics, performance (personal/team), trends, and GET/POST for reports (history, generate, schedule). Include auth via verifyAgentAuth, DB access, query params, computed metrics, chart data, and structured responses with error handling.
Agent Dashboard API & UI
src/app/api/agent/dashboard/route.ts, src/components/agent/dashboard/StatsOverview.tsx
Added dashboard GET route returning stats (mock-backed). StatsOverview now fetches from API, manages loading, and periodically refreshes; replaces static values with API-driven ones.
Agent Analytics UI Components
src/components/agent/analytics/AnalyticsDashboard.tsx, .../PerformanceMetrics.tsx, .../ReportGenerator.tsx, .../SystemTrends.tsx, .../SimplifiedAnalytics.tsx
Converted from mocks to live data fetching from new APIs, added loading states, state containers, and effects. Introduced new SimplifiedAnalytics component aggregating three endpoints. Updated handlers to call reports API and render dynamic histories and charts.
Agent Analytics Page
src/app/agent/analytics/page.tsx
Replaced AnalyticsDashboard usage with SimplifiedAnalytics import and render.
Auth Middleware Helpers
src/lib/auth/agent-middleware.ts, src/lib/auth/admin-middleware.ts
Added verifyAgentAuth and verifyAdminAuth helpers returning simplified auth results (and admin fetch) for API routes.
Admin Management
src/app/admin/admin-management/page.tsx
Memoized fetchAdmins with useCallback and updated effect dependencies; logic unchanged.
Debug Route Signatures
src/app/api/debug/check-admins/route.ts, src/app/api/debug/setup-admins/route.ts
Removed NextRequest parameter from handlers; internal logic unchanged.
User Pages Adjustments
src/app/user/auth/register/page.tsx, src/app/user/booking/new/page.tsx, src/app/user/dashboard/page.tsx
Register: PasswordStrengthIndicator no longer takes language prop. Booking: removed local error type and name-lookup helpers. Dashboard: removed color prop from ServiceCard and its usages.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Agent as Agent (Browser)
  participant Simplified as SimplifiedAnalytics (UI)
  participant API1 as /api/agent/analytics
  participant API2 as /api/agent/analytics/performance
  participant API3 as /api/agent/analytics/trends
  participant Auth as verifyAgentAuth
  participant DB as Database

  Agent->>Simplified: Load page / set timeRange
  Simplified->>API1: GET ?timeRange=...
  Simplified->>API2: GET ?timeRange=...&view=personal
  Simplified->>API3: GET ?timeRange=...
  API1->>Auth: Verify
  API2->>Auth: Verify
  API3->>Auth: Verify
  Auth-->>API1: isValid, agent
  Auth-->>API2: isValid, agent
  Auth-->>API3: isValid, agent
  API1->>DB: Query appointments
  API2->>DB: Query appointments/agents
  API3->>DB: Query appointments
  DB-->>API1: Results
  DB-->>API2: Results
  DB-->>API3: Results
  API1-->>Simplified: quickStats, chartData, totals
  API2-->>Simplified: personalMetrics, chartData, skills/team
  API3-->>Simplified: trends, peak hours, resources, queries
  Simplified-->>Agent: Render dashboard
Loading
sequenceDiagram
  autonumber
  actor Agent as Agent (Browser)
  participant Reports as /api/agent/analytics/reports
  participant Auth as verifyAgentAuth
  participant DB as Database

  Agent->>Reports: GET ?action=getHistory
  Reports->>Auth: Verify
  Auth-->>Reports: isValid, agent
  Reports->>DB: Fetch recent appointments (agent)
  DB-->>Reports: Records
  Reports-->>Agent: recentReports

  Agent->>Reports: POST { action: generateReport, reportConfig }
  Reports->>Auth: Verify
  Auth-->>Reports: isValid, agent
  Reports->>DB: Fetch appointments in range
  DB-->>Reports: Records
  Reports-->>Agent: reportId, reportData, downloadUrl
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Dev #76 — Overlaps on agent analytics routes and components; likely touches the same files and data flow.
  • Dev #61 — Modifies admin-management page; intersects with this PR’s fetchAdmins refactor.
  • Api adeepa #53 — Adds/changes agent auth middleware; directly related to new verifyAgentAuth usage.

Suggested reviewers

  • AdeepaK2
  • mehara-rothila

Poem

A whisk of keys, a dash of stats,
I hop through charts and backend chats.
New routes bloom like clover bright,
Dashboards nibble fresh insight.
Reports? Retrieved! Trends? Alight!
Thump-thump—ship it, feels just right. 🐇📊✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch aganet--analytics

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR integrates agent analytics functionality with the backend by replacing mock data with real API calls. The main purpose is to connect frontend analytics components with actual server-side data endpoints and introduce new simplified analytics dashboards.

  • Replace static mock data in agent analytics components with dynamic API calls
  • Add new analytics API routes for dashboard stats, trends, performance metrics, and report generation
  • Introduce a new SimplifiedAnalytics component for a more streamlined analytics view
  • Clean up unused code and improve authentication middleware

Reviewed Changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/lib/auth/agent-middleware.ts Adds simplified agent verification function for API routes
src/lib/auth/admin-middleware.ts Adds admin authentication verification function for API consistency
src/components/agent/dashboard/StatsOverview.tsx Replaces mock stats with real API data from /api/agent/dashboard
src/components/agent/analytics/SystemTrends.tsx Integrates with trends API and replaces hardcoded data
src/components/agent/analytics/SimplifiedAnalytics.tsx New component providing simplified analytics dashboard with API integration
src/components/agent/analytics/ReportGenerator.tsx Connects to reports API for generation and history
src/components/agent/analytics/PerformanceMetrics.tsx Replaces mock performance data with API calls
src/components/agent/analytics/AnalyticsDashboard.tsx Updates main analytics dashboard with real data
src/app/api/agent/dashboard/route.ts New API endpoint for agent dashboard statistics
src/app/api/agent/analytics/trends/route.ts New API endpoint for system trends and analysis
src/app/api/agent/analytics/route.ts New API endpoint for core analytics data
src/app/api/agent/analytics/reports/route.ts New API endpoint for report generation and management
src/app/api/agent/analytics/performance/route.ts New API endpoint for performance metrics

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


// Form Components
const PasswordStrengthIndicator = ({ password, language = 'en' }: { password: string; language?: Language }) => {
const PasswordStrengthIndicator = ({ password }: { password: string; language?: Language }) => {
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

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

The language parameter is defined in the destructured props but not used in the component implementation, yet it's still present in the type definition. Either remove it from the type or use it in the getStrength function.

Suggested change
const PasswordStrengthIndicator = ({ password }: { password: string; language?: Language }) => {
const PasswordStrengthIndicator = ({ password }: { password: string }) => {

Copilot uses AI. Check for mistakes.
<div className="flex items-center justify-between mb-3">
<h4 className="text-sm font-medium text-muted-foreground group-hover:text-foreground transition-colors duration-300">
{t[key]}
{t[key as keyof MetricsTranslation]}
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

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

The type assertion 'as keyof MetricsTranslation' is unsafe because 'key' comes from Object.keys(personalMetrics) which may not match the keys in MetricsTranslation. This could cause runtime errors if the keys don't align.

Copilot uses AI. Check for mistakes.
<span>•</span>
<span>{report.size}</span>
<span>•</span>
<span>{formatDate(report.generatedAt)}</span>
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

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

The property name has changed from 'generatedDate' to 'generatedAt' in the Report interface, but this change may not be consistent throughout the codebase. Verify that all backend APIs return 'generatedAt' instead of 'generatedDate'.

Suggested change
<span>{formatDate(report.generatedAt)}</span>
<span>{formatDate(report.generatedAt ?? report.generatedDate)}</span>

Copilot uses AI. Check for mistakes.
import bcrypt from "bcryptjs";

export async function POST(request: NextRequest) {
export async function POST() {
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

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

The request parameter was removed but this endpoint should validate the request or require authentication to prevent unauthorized admin creation. Consider adding proper authentication or request validation.

Copilot uses AI. Check for mistakes.
import Admin from "@/lib/models/adminSchema";

export async function GET(request: NextRequest) {
export async function GET() {
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

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

This debug endpoint exposes admin information without any authentication. This could be a security risk in production environments.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@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: 9

🔭 Outside diff range comments (6)
src/app/api/debug/setup-admins/route.ts (2)

6-12: Lock down this debug endpoint in non-development environments

This route seeds accounts and exposes sensitive data. It must be disabled outside development to prevent accidental production execution.

Apply this diff to guard the endpoint:

 export async function POST() {
   try {
+    // Hard stop for non-development environments
+    if (process.env.NODE_ENV !== "development") {
+      return NextResponse.json(
+        { success: false, message: "setup-admins is disabled outside development" },
+        { status: 403 }
+      );
+    }
+
     await connectDB();

97-118: Do not return plaintext credentials in API responses

Returning seeded admin credentials is a serious security/PII issue, even for “debug” routes. If you need the credentials for local dev, conditionally include them only in development; otherwise omit entirely.

Apply this diff to conditionally include credentials only in development:

-    return NextResponse.json({
-      success: true,
-      message: "Admin setup completed successfully",
-      admins: allAdmins.map((admin) => ({
-        id: admin._id,
-        fullName: admin.fullName,
-        email: admin.email,
-        role: admin.role,
-        accountStatus: admin.accountStatus,
-        createdAt: admin.createdAt,
-      })),
-      credentials: {
-        superAdmin: {
-          email: "superadmin@govlink.lk",
-          password: "ThisIsInsane",
-        },
-        regularAdmin: {
-          email: "admin@govlink.lk",
-          password: "TestAdmin123",
-        },
-      },
-    });
+    const payload: any = {
+      success: true,
+      message: "Admin setup completed successfully",
+      admins: allAdmins.map((admin) => ({
+        id: admin._id.toString(),
+        fullName: admin.fullName,
+        email: admin.email,
+        role: admin.role,
+        accountStatus: admin.accountStatus,
+        createdAt: admin.createdAt,
+      })),
+    };
+    if (process.env.NODE_ENV === "development") {
+      payload.credentials = {
+        superAdmin: { email: "superadmin@govlink.lk", password: "ThisIsInsane" },
+        regularAdmin: { email: "admin@govlink.lk", password: "TestAdmin123" },
+      };
+    }
+    return NextResponse.json(payload);
src/components/agent/analytics/SystemTrends.tsx (4)

297-301: Guard TrendChart against empty datasets to avoid -Infinity/NaN heights

When data is empty, Math.max(...[]) yields -Infinity and height math can be undefined. Add an early return and a safe max baseline.

Apply this diff:

-  const TrendChart = ({ data, metric, color }: { data: TrendDataPoint[], metric: keyof TrendDataPoint, color: string }) => {
-    const maxValue = Math.max(...data.map(d => d[metric] as number));
+  const TrendChart = ({ data, metric, color }: { data: TrendDataPoint[], metric: keyof TrendDataPoint, color: string }) => {
+    if (!data || data.length === 0) {
+      return <div className="h-40 flex items-center justify-center text-sm text-muted-foreground">No data</div>;
+    }
+    const maxValue = Math.max(1, ...data.map(d => d[metric] as number));

400-413: Prevent -Infinity/NaN in Peak/Avg/Low traffic when trendsData is empty

Rendering with empty trendsData shows -Infinity/Infinity/NaN. Guard calculations.

Apply this diff:

-              <div className="font-bold text-[#FFC72C]">{Math.max(...trendsData.map(d => d.traffic))}</div>
+              <div className="font-bold text-[#FFC72C]">{trendsData.length ? Math.max(...trendsData.map(d => d.traffic)) : 0}</div>
...
-                {Math.round(trendsData.reduce((acc, d) => acc + d.traffic, 0) / trendsData.length)}
+                {trendsData.length ? Math.round(trendsData.reduce((acc, d) => acc + d.traffic, 0) / trendsData.length) : 0}
...
-              <div className="font-bold text-[#008060]">{Math.min(...trendsData.map(d => d.traffic))}</div>
+              <div className="font-bold text-[#008060]">{trendsData.length ? Math.min(...trendsData.map(d => d.traffic)) : 0}</div>

423-437: Prevent -Infinity/NaN in Response Time stats when trendsData is empty

Same issue for responseTime max/avg/min. Guard the math.

Apply this diff:

-                {Math.max(...trendsData.map(d => d.responseTime)).toFixed(1)}s
+                {(trendsData.length ? Math.max(...trendsData.map(d => d.responseTime)) : 0).toFixed(1)}s
...
-                {(trendsData.reduce((acc, d) => acc + d.responseTime, 0) / trendsData.length).toFixed(1)}s
+                {(trendsData.length ? (trendsData.reduce((acc, d) => acc + d.responseTime, 0) / trendsData.length) : 0).toFixed(1)}s
...
-                {Math.min(...trendsData.map(d => d.responseTime)).toFixed(1)}s
+                {(trendsData.length ? Math.min(...trendsData.map(d => d.responseTime)) : 0).toFixed(1)}s

447-447: Avoid mutating state-derived arrays during render

sort() mutates the original array (systemData.peakHoursData). Clone before sorting to prevent accidental state mutation in render.

Apply this diff:

-          {peakHoursData.sort((a, b) => b.traffic - a.traffic).map((hour) => (
+          {[...peakHoursData].sort((a, b) => b.traffic - a.traffic).map((hour) => (
🧹 Nitpick comments (36)
src/app/api/debug/setup-admins/route.ts (1)

100-107: Serialize ObjectId to string to avoid JSON serialization quirks

Mongoose ObjectId should be stringified for consistent API responses.

Apply this diff:

-        id: admin._id,
+        id: admin._id.toString(),
src/app/user/auth/register/page.tsx (1)

199-231: Either localize the strength labels or drop the unused language prop

Right now, PasswordStrengthIndicator accepts a language prop in its type but ignores it at runtime, while callers still pass it. Either remove the prop end-to-end or use it to localize labels.

Apply this diff to localize labels and keep the API clear:

-const PasswordStrengthIndicator = ({ password }: { password: string; language?: Language }) => {
+const PasswordStrengthIndicator = ({ password, language = 'en' }: { password: string; language?: Language }) => {
+  const strengthLabels: Record<Language, { weak: string; fair: string; strong: string }> = {
+    en: { weak: 'Weak', fair: 'Fair', strong: 'Strong' },
+    si: { weak: 'දුර්වල', fair: 'සාමාන්‍ය', strong: 'දृඝ' },
+    ta: { weak: 'பலவீனமானது', fair: 'சராசரி', strong: 'வலிமையானது' },
+  };
+
   const getStrength = () => {
     if (password.length === 0) return { score: 0, label: '', color: '' };
-    if (password.length < 6) return { score: 1, label: 'Weak', color: '#FF5722' };
-    if (password.length < 10) return { score: 2, label: 'Fair', color: '#FFC72C' };
-    return { score: 3, label: 'Strong', color: '#008060' };
+    if (password.length < 6) return { score: 1, label: strengthLabels[language].weak, color: '#FF5722' };
+    if (password.length < 10) return { score: 2, label: strengthLabels[language].fair, color: '#FFC72C' };
+    return { score: 3, label: strengthLabels[language].strong, color: '#008060' };
   };
src/app/api/debug/check-admins/route.ts (2)

14-14: Avoid logging full admin documents

Logging the entire admin array leaks PII into logs. Log the count instead.

Apply this diff:

-    console.log("Found admins:", admins);
+    console.log("Found admins:", admins.length);

20-21: Stringify ObjectId in API response

Ensure id is a string for client consumption and consistent typing.

Apply this diff:

-        id: admin._id,
+        id: admin._id.toString(),
src/lib/auth/admin-middleware.ts (1)

192-214: Avoid redundant DB connect and select safe fields; handle deleted admin

authenticateAdmin already connects to the DB. Also, selecting only safe fields reduces accidental exposure downstream and handling a not-found edge makes the helper more robust.

Apply this diff:

-export const verifyAdminAuth = async (request: NextRequest) => {
+export const verifyAdminAuth = async (request: NextRequest) => {
   const authResult = await authenticateAdmin(request);
   
   if (!authResult.success) {
     return {
       isValid: false,
       admin: null,
       error: authResult.message
     };
   }
 
-  // Fetch full admin data
-  await connectDB();
-  const admin = await Admin.findById(authResult.admin!.userId);
+  // Fetch admin data (select safe fields)
+  const admin = await Admin.findById(authResult.admin!.userId)
+    .select("fullName email role accountStatus lastLoginAt createdAt updatedAt");
+  if (!admin) {
+    return {
+      isValid: false,
+      admin: null,
+      error: "Admin not found"
+    };
+  }
   
   return {
     isValid: true,
     admin,
     error: null
   };
 };
src/app/admin/admin-management/page.tsx (1)

85-104: Consider toggling loading state at the start of fetchAdmins (for refresh UX) and add abort support

Current implementation only sets loading to false in finally. If you ever reuse fetchAdmins to refresh the list after create/update/delete, you won’t show a loading indicator. Also, adding an AbortController prevents setState on unmounted components during navigation.

Apply this diff to show refresh loading and support abort:

-  const fetchAdmins = useCallback(async () => {
-    try {
+  const fetchAdmins = useCallback(async () => {
+    const controller = new AbortController();
+    try {
+      setLoading(true);
       const response = await fetch("/api/admin/admins", {
         method: "GET",
-        headers: getAuthHeaders(),
+        headers: getAuthHeaders(),
         credentials: "include",
+        signal: controller.signal,
       });
 
       if (response.ok) {
         const data = await response.json();
         setAdmins(data.admins);
       } else {
         console.error("Failed to fetch admins");
       }
     } catch (error) {
       console.error("Error fetching admins:", error);
     } finally {
       setLoading(false);
     }
-  }, [getAuthHeaders]);
+    return () => controller.abort();
+  }, [getAuthHeaders]);
src/app/api/agent/analytics/performance/route.ts (2)

62-63: Clamp firstContactRate to 100 to avoid >100% values in UI

With resolutionRate near 100, 60 + 0.3*resolutionRate can exceed 100. Clamp to keep percentages valid.

-      const firstContactRate = totalAppointments > 0 ? 
-        (60 + (resolutionRate * 0.3)) : 0;
+      const firstContactRate = totalAppointments > 0
+        ? Math.min(100, 60 + resolutionRate * 0.3)
+        : 0;

150-169: Avoid N+1 queries when building team rankings

The per-agent Appointment.find call inside map causes N+1 DB roundtrips. Consider an aggregation to group by assignedAgent over the time window and compute counts in one query, then join to the agents list in memory.

src/lib/auth/agent-middleware.ts (1)

175-210: Prefer excluding id when spreading agent to avoid duplicate identifiers

verifyAgentAuth currently spreads authResult.agent, which includes id, after adding _id. This creates both id and _id on the returned object. While generally harmless, eliminating id avoids confusion and accidental downstream reliance.

Apply this diff:

-  return {
-    isValid: true,
-    agent: {
-      _id: authResult.agent.id,
-      ...authResult.agent
-    }
-  };
+  const { id, ...agentRest } = authResult.agent;
+  return {
+    isValid: true,
+    agent: {
+      _id: id,
+      ...agentRest,
+    },
+  };
src/components/agent/dashboard/StatsOverview.tsx (3)

120-124: Remove console logs before shipping

These logs are useful during development but noisy in production. Consider gating with NODE_ENV or removing.

-      const result = await response.json();
-      console.log('Dashboard API Response:', result);
-      console.log('Dashboard Data:', result.data);
-      setDashboardData(result.data);
+      const result = await response.json();
+      setDashboardData(result.data);

66-95: Unused confirmedAppointments in DashboardData

Type includes confirmedAppointments but no card renders it. Either add a card or remove it from the type to reduce confusion.

Would you prefer we add a Confirmed Appointments card or drop the field from this component’s local type to match current UI?


106-137: Optional: abort in-flight fetches on unmount or refresh

To avoid setState on unmounted component edge cases, wrap fetchStats with AbortController and check signal.aborted before updating state.

I can provide a patch using AbortController if you want to harden fetchStats.

src/components/agent/analytics/SystemTrends.tsx (3)

346-358: selectedMetric is not wired to the chart; hook it up for expected behavior

Buttons update selectedMetric but the primary TrendChart always renders traffic. Map selectedMetric to the appropriate metric field.

Apply this diff:

-          <TrendChart data={trendsData} metric="traffic" color="#FFC72C" />
+          <TrendChart 
+            data={trendsData} 
+            metric={
+              selectedMetric === 'traffic' ? 'traffic' :
+              selectedMetric === 'performance' ? 'responseTime' :
+              selectedMetric === 'resources' ? 'cpuUsage' : 
+              'errorRate'
+            } 
+            color="#FFC72C" 
+          />

Also applies to: 398-398


253-275: Cancel in-flight fetches on timeRange changes/unmount to avoid state updates on unmounted components

Use AbortController and check signal.aborted before setting state. This also avoids racing responses overwriting newer data.

I can provide a patch adding AbortController and cleanup in useEffect if you’d like.


176-226: Minor i18n nit: Mixed-script characters in Tamil translation

There appears to be a Devanagari segment in a Tamil string (line 207). Please review and correct the mixed script.

src/components/agent/analytics/AnalyticsDashboard.tsx (1)

123-146: Set loading on re-fetch to avoid showing stale data during timeRange changes

Currently only isRefreshing is toggled; the page-level spinner never shows after the initial load. Optionally set loading true before fetch to present a consistent UX on timeRange changes.

Apply this diff:

-    try {
-      setIsRefreshing(true);
+    try {
+      setIsRefreshing(true);
+      setLoading(true);
src/app/api/agent/dashboard/route.ts (2)

64-70: Unify trend payload shape for consistent consumers

Using a literal "Scheduled" for trend.value breaks consistency with percentage changes used elsewhere. Consider standardizing trend.value to a delta-like string (e.g., "+0%") and adding a separate status if needed.

Please confirm how StatsOverview (or other consumers) expect trend for confirmedAppointments. If they expect a percent-like value, I can provide a patch to align this field.


13-14: Optional: Skip DB connection when returning mock data

connectDB is unnecessary here until real queries are added, and skipping it reduces overhead.

src/components/agent/analytics/PerformanceMetrics.tsx (2)

174-196: Cancel in-flight requests to prevent race conditions and state updates on unmounted

Introduce AbortController and clean up in useEffect. This avoids stale responses overriding newer state.

I can supply a patch adding AbortController wiring if you want to adopt it here and in sibling analytics components for consistency.


284-285: Safer fallback for missing translation keys

If the backend adds a metric without a translation key, this will render undefined. Provide a fallback to the key string.

Apply this diff:

-                    {t[key as keyof MetricsTranslation]}
+                    {t[key as keyof MetricsTranslation] ?? String(key)}
src/app/api/agent/analytics/route.ts (1)

42-46: Reduce query payload for performance

Only a few fields are used (status, createdAt, serviceType). Use select(...).lean() to cut transfer and de-hydration overhead.

Example:

const agentAppointments = await Appointment.find(
  { assignedAgent: agentId, createdAt: { $gte: startDate, $lte: now } },
  'status createdAt serviceType'
).lean();

Also consider adding a compound index on { assignedAgent: 1, createdAt: 1 } in the Appointment schema to optimize these range queries.

src/app/api/agent/analytics/trends/route.ts (5)

42-45: Select only needed fields to reduce memory and serialization cost

You only use createdAt, serviceType, and status downstream. Limit the projection to reduce payload and speed up serialization.

-    const appointments = await Appointment.find({
-      assignedAgent: agentId,
-      createdAt: { $gte: startDate, $lte: now }
-    });
+    const appointments = await Appointment.find({
+      assignedAgent: agentId,
+      createdAt: { $gte: startDate, $lte: now }
+    }).select('createdAt serviceType status');

131-131: More robust label formatting for service types

Replace only-first-underscore replace('_', ' ') with a global underscore replacement and proper title-casing.

-          type: type.charAt(0).toUpperCase() + type.slice(1).replace('_', ' '),
+          type: type
+            .replace(/_/g, ' ')
+            .replace(/\b\w/g, (c) => c.toUpperCase()),

86-106: Optional: Pre-binning for peak hour calculation

Current approach is O(24 × N). For larger datasets, pre-binning into a 24-length counter array in a single pass will reduce complexity to O(N).

If you want, I can provide a refactor that builds a frequency map by hour in one pass and derives peak hours from it.


140-170: Clarify errorRate semantics

errorRate is a 0–2 value depending on loadFactor; consumers likely expect a percentage (0–100). Consider normalizing and documenting the unit.


41-45: Indexing advice for query performance

Make sure Appointment has a compound index on { assignedAgent: 1, createdAt: 1 } to keep these range queries fast as data grows.

src/app/api/agent/analytics/reports/route.ts (1)

125-147: Honor custom endDate and make endDate explicit

In custom period, endDate from the request is ignored. Also make endDate explicit for all periods to avoid relying on a closed-over now.

-      const now = new Date();
-      let startDate = new Date();
-      
-      switch (reportConfig.period) {
+      const now = new Date();
+      let startDate = new Date();
+      let endDate = now;
+      const period = reportConfig?.period;
+
+      switch (period) {
         case 'daily':
           startDate.setHours(0, 0, 0, 0);
           break;
         case 'weekly':
           startDate.setDate(now.getDate() - 7);
           break;
         case 'monthly':
           startDate.setMonth(now.getMonth() - 1);
           break;
         case 'quarterly':
           startDate.setMonth(now.getMonth() - 3);
           break;
         case 'custom':
           startDate = new Date(reportConfig.startDate);
+          if (reportConfig.endDate) {
+            endDate = new Date(reportConfig.endDate);
+          }
           break;
         default:
           startDate.setDate(now.getDate() - 7);
       }

Note: The subsequent query/filter should use endDate instead of now (see previous comment).

src/components/agent/analytics/SimplifiedAnalytics.tsx (5)

205-216: Mislabelled stat: shows satisfactionRate under "Completion Rate"

This card renders satisfactionRate but labels it "Completion Rate". Either switch to resolutionRate or rename the title to "Satisfaction Rate" for clarity.

-          {
-            title: 'Completion Rate',
-            value: `${quickStats.satisfactionRate?.value || '0'}${quickStats.satisfactionRate?.unit || '%'}`,
+          {
+            title: 'Satisfaction Rate',
+            value: `${quickStats.satisfactionRate?.value || '0'}${quickStats.satisfactionRate?.unit || '%'}`,
             change: quickStats.satisfactionRate?.change || '0%',
             isPositive: quickStats.satisfactionRate?.isPositive || false,

253-254: Make the section header reflect the selected time range

Avoid “Weekly” when the timeRange is today/month/quarter.

-          <h3 className="text-lg font-semibold text-foreground">Weekly Performance</h3>
+          <h3 className="text-lg font-semibold text-foreground">
+            {timeRange.charAt(0).toUpperCase() + timeRange.slice(1)} Performance
+          </h3>

447-448: Avoid rendering 'undefined' when unit is missing

Default the unit to an empty string.

-                {metric.value}{metric.unit}
+                {metric.value}{metric.unit ?? ''}

93-94: Avoid noisy logs in production

Wrap console logs with a NODE_ENV check so production consoles remain clean.

-        console.log('Analytics data received:', result);
+        if (process.env.NODE_ENV !== 'production') {
+          console.log('Analytics data received:', result);
+        }
-        console.log('Performance data received:', result);
+        if (process.env.NODE_ENV !== 'production') {
+          console.log('Performance data received:', result);
+        }
-        console.log('Trends data received:', result);
+        if (process.env.NODE_ENV !== 'production') {
+          console.log('Trends data received:', result);
+        }

Also applies to: 102-103, 111-112


72-90: Optional: Prevent race conditions on rapid timeRange changes

Add an AbortController to cancel in-flight requests when timeRange changes to avoid setting state from stale responses.

I can provide a small refactor using AbortController if you want to pursue this now.

Also applies to: 124-127

src/components/agent/analytics/ReportGenerator.tsx (4)

296-298: Remove duplicated comment

The “Fetch real report data from backend” comment appears twice consecutively.

-  // Fetch real report data from backend
-  // Fetch real report data from backend
+  // Fetch real report data from backend

523-528: Hook "Generate Now" buttons to actions for accessibility

Right now generation is only bound to the card onClick. Also attach onClick to the button so keyboard users can trigger it.

                   <button
                     disabled={isGenerating}
+                    onClick={(e) => { e.stopPropagation(); handleQuickReport('performance'); }}
                     className="w-full px-4 py-2 bg-gradient-to-r from-[#FFC72C] to-[#FF5722] text-white rounded-lg hover:from-[#FF5722] hover:to-[#8D153A] transition-all duration-300 disabled:opacity-50 font-medium shadow-lg hover:shadow-xl"
                   >
                   <button
                     disabled={isGenerating}
+                    onClick={(e) => { e.stopPropagation(); handleQuickReport('service'); }}
                     className="w-full px-4 py-2 bg-gradient-to-r from-[#008060] to-[#FFC72C] text-white rounded-lg hover:from-[#FFC72C] hover:to-[#FF5722] transition-all duration-300 disabled:opacity-50 font-medium shadow-lg hover:shadow-xl"
                   >
                   <button
                     disabled={isGenerating}
+                    onClick={(e) => { e.stopPropagation(); handleQuickReport('operational'); }}
                     className="w-full px-4 py-2 bg-gradient-to-r from-[#FF5722] to-[#8D153A] text-white rounded-lg hover:from-[#8D153A] hover:to-[#FF5722] transition-all duration-300 disabled:opacity-50 font-medium shadow-lg hover:shadow-xl"
                   >

Also applies to: 578-583, 632-637


330-358: Differentiate success message for scheduled vs generated reports

Currently you always show “Report generated successfully!” even when scheduling. Use the schedule selection to show t.reportScheduled for the scheduled flow.

I can provide a small state tweak (e.g., successMessage) and wire it into the success toast if you’d like.


373-381: Check and handle non-OK responses for quick reports

You ignore non-OK responses; consider logging and surfacing an error toast to match the generate flow.

-      if (response.ok) {
+      if (response.ok) {
         setShowSuccess(true);
         setTimeout(() => setShowSuccess(false), 3000);
         
         // Refresh report history
         await fetchReportData();
-      }
+      } else {
+        console.error('Failed to generate quick report:', response.status, await response.text());
+      }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6279453 and a4fb7b8.

📒 Files selected for processing (20)
  • src/app/admin/admin-management/page.tsx (4 hunks)
  • src/app/agent/analytics/page.tsx (2 hunks)
  • src/app/api/agent/analytics/performance/route.ts (1 hunks)
  • src/app/api/agent/analytics/reports/route.ts (1 hunks)
  • src/app/api/agent/analytics/route.ts (1 hunks)
  • src/app/api/agent/analytics/trends/route.ts (1 hunks)
  • src/app/api/agent/dashboard/route.ts (1 hunks)
  • src/app/api/debug/check-admins/route.ts (1 hunks)
  • src/app/api/debug/setup-admins/route.ts (1 hunks)
  • src/app/user/auth/register/page.tsx (1 hunks)
  • src/app/user/booking/new/page.tsx (0 hunks)
  • src/app/user/dashboard/page.tsx (1 hunks)
  • src/components/agent/analytics/AnalyticsDashboard.tsx (6 hunks)
  • src/components/agent/analytics/PerformanceMetrics.tsx (3 hunks)
  • src/components/agent/analytics/ReportGenerator.tsx (8 hunks)
  • src/components/agent/analytics/SimplifiedAnalytics.tsx (1 hunks)
  • src/components/agent/analytics/SystemTrends.tsx (2 hunks)
  • src/components/agent/dashboard/StatsOverview.tsx (5 hunks)
  • src/lib/auth/admin-middleware.ts (1 hunks)
  • src/lib/auth/agent-middleware.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/app/user/booking/new/page.tsx
🧰 Additional context used
🧬 Code Graph Analysis (12)
src/app/api/agent/analytics/performance/route.ts (3)
src/lib/auth/agent-middleware.ts (1)
  • verifyAgentAuth (178-210)
src/lib/services/appointmentService.ts (1)
  • Appointment (5-20)
src/lib/services/departmentApiService.ts (1)
  • Agent (46-72)
src/app/api/debug/check-admins/route.ts (3)
src/app/api/admin/admins/[id]/route.ts (1)
  • GET (19-77)
src/app/api/admin/admins/route.ts (1)
  • GET (8-57)
src/app/api/auth/admin/route.ts (1)
  • GET (78-131)
src/app/api/agent/dashboard/route.ts (5)
src/app/api/debug/check-admins/route.ts (1)
  • GET (5-39)
src/app/api/agent/analytics/performance/route.ts (1)
  • GET (7-212)
src/app/api/agent/analytics/route.ts (1)
  • GET (6-164)
src/app/api/agent/analytics/trends/route.ts (1)
  • GET (6-228)
src/lib/auth/agent-middleware.ts (1)
  • verifyAgentAuth (178-210)
src/app/api/agent/analytics/route.ts (5)
src/app/api/agent/analytics/reports/route.ts (1)
  • GET (6-102)
src/app/api/agent/analytics/performance/route.ts (1)
  • GET (7-212)
src/app/api/agent/analytics/trends/route.ts (1)
  • GET (6-228)
src/lib/auth/agent-middleware.ts (1)
  • verifyAgentAuth (178-210)
src/lib/services/appointmentService.ts (1)
  • Appointment (5-20)
src/app/api/agent/analytics/trends/route.ts (8)
src/app/api/debug/check-admins/route.ts (1)
  • GET (5-39)
src/app/api/agent/analytics/reports/route.ts (1)
  • GET (6-102)
src/app/api/agent/analytics/performance/route.ts (1)
  • GET (7-212)
src/app/api/agent/analytics/route.ts (1)
  • GET (6-164)
src/lib/auth/agent-middleware.ts (1)
  • verifyAgentAuth (178-210)
src/lib/services/appointmentService.ts (1)
  • Appointment (5-20)
scripts/migrate-departments.js (3)
  • h (302-302)
  • a (256-256)
  • type (373-373)
scripts/check-database.js (1)
  • count (95-97)
src/app/api/debug/setup-admins/route.ts (3)
src/app/api/auth/admin/login/route.ts (1)
  • POST (7-64)
src/app/api/admin/admins/route.ts (1)
  • POST (60-151)
src/app/api/auth/admin/route.ts (1)
  • POST (10-75)
src/components/agent/dashboard/StatsOverview.tsx (1)
src/lib/services/departmentApiService.ts (1)
  • DashboardData (24-31)
src/lib/auth/agent-middleware.ts (1)
src/lib/auth/agent-auth.ts (1)
  • authenticateAgent (7-7)
src/lib/auth/admin-middleware.ts (1)
src/lib/auth/AdminAuthContext.tsx (1)
  • Admin (9-16)
src/app/api/agent/analytics/reports/route.ts (6)
src/app/api/agent/dashboard/route.ts (1)
  • GET (5-92)
src/app/api/agent/analytics/performance/route.ts (1)
  • GET (7-212)
src/app/api/agent/analytics/route.ts (1)
  • GET (6-164)
src/app/api/agent/analytics/trends/route.ts (1)
  • GET (6-228)
src/lib/auth/agent-middleware.ts (1)
  • verifyAgentAuth (178-210)
src/lib/services/appointmentService.ts (1)
  • Appointment (5-20)
src/app/admin/admin-management/page.tsx (1)
src/lib/services/departmentApiService.ts (1)
  • getAuthHeaders (113-119)
src/components/agent/analytics/SimplifiedAnalytics.tsx (1)
src/lib/services/departmentApiService.ts (1)
  • AnalyticsData (97-103)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build-Test
🔇 Additional comments (10)
src/app/user/dashboard/page.tsx (2)

277-349: ServiceCard simplification looks good

Removing the color prop from the ServiceCard API and inlining the style palette simplifies the component surface without loss of functionality.


277-349: No stale color prop on ServiceCard found

I searched all TSX files and confirmed there are no <ServiceCard color=…> usages. The only color= props live in TrendChart calls (e.g. in src/components/agent/analytics/SystemTrends.tsx), so you can consider the removal of color from ServiceCard complete.

src/app/admin/admin-management/page.tsx (2)

3-3: Importing useCallback is appropriate for stabilizing fetch function identity

Good call adding useCallback to avoid effect churn and unnecessary re-renders when passing the fetch function as a dependency.


106-113: Effect dependency updated to include fetchAdmins — good

This prevents stale closure issues and ensures the effect re-runs if the headers function changes.

src/app/agent/analytics/page.tsx (1)

5-5: Switching to SimplifiedAnalytics component is straightforward and consistent

Import and usage look correct. Assuming SimplifiedAnalytics accepts a language prop, this change is safe.

Also applies to: 52-52

src/app/api/agent/analytics/performance/route.ts (1)

50-56: No schema mismatches found for Appointment.status, assignedAgent, or createdAt
APPOINTMENT_STATUS includes 'completed', and the AppointmentSchema defines both assignedAgent and createdAt (via explicit field and timestamps: true). Filters against apt.status === 'completed' will work as intended.

src/components/agent/analytics/AnalyticsDashboard.tsx (1)

165-229: LGTM: Quick stats wiring to backend is clean and guarded

The mapping from analyticsData.quickStats to UI looks correct, with safe fallback to empty array.

src/components/agent/analytics/PerformanceMetrics.tsx (1)

218-224: LGTM: Loading guard prevents chart math on empty data

Early return avoids Math.max on empty arrays; this prevents Infinity/NaN issues in charts.

src/app/api/agent/analytics/route.ts (1)

57-63: Resolution rate equals satisfaction rate — confirm intent

Both satisfactionRate and resolutionRate are derived from completed/total. If satisfaction should come from ratings, this may mislead consumers.

Would you like me to adjust resolutionRate/satisfactionRate computation (e.g., use a rating field if present, or distinguish “resolved” vs “completed”)?

src/app/api/agent/analytics/reports/route.ts (1)

20-57: LGTM: GET action handlers structure and error handling

Auth gate, action routing, DB connect, and structured 401/400/500 responses are consistent with other agent APIs.

Comment on lines +182 to +201
const teamAvgScore = teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length;

const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({
period: i + 1,
responseTime: 3.2 + (Math.sin(i * 0.4) * 1.0),
satisfaction: Math.max(75, teamAvgScore + (Math.cos(i * 0.3) * 8)),
resolution: Math.max(70, teamAvgScore + (Math.sin(i * 0.5) * 6)),
tickets: Math.max(2, Math.round(15 + (Math.sin(i * 0.6) * 5)))
}));

return NextResponse.json({
success: true,
data: {
view: 'team',
teamRankingData,
chartData,
timeRange
}
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard against empty teams to avoid NaN in JSON (division by zero)

When departmentAgents is empty, teamAvgScore becomes NaN (0/0), producing nulls in JSON for chart values. This degrades the UI.

Apply this diff to early-return a well-formed empty dataset:

-      const teamAvgScore = teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length;
-
-      const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({
-        period: i + 1,
-        responseTime: 3.2 + (Math.sin(i * 0.4) * 1.0),
-        satisfaction: Math.max(75, teamAvgScore + (Math.cos(i * 0.3) * 8)),
-        resolution: Math.max(70, teamAvgScore + (Math.sin(i * 0.5) * 6)),
-        tickets: Math.max(2, Math.round(15 + (Math.sin(i * 0.6) * 5)))
-      }));
-
-      return NextResponse.json({
-        success: true,
-        data: {
-          view: 'team',
-          teamRankingData,
-          chartData,
-          timeRange
-        }
-      });
+      if (teamRankingData.length === 0) {
+        return NextResponse.json({
+          success: true,
+          data: {
+            view: 'team',
+            teamRankingData: [],
+            chartData: [],
+            timeRange
+          }
+        });
+      }
+
+      const teamAvgScore =
+        teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length;
+
+      const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({
+        period: i + 1,
+        responseTime: 3.2 + Math.sin(i * 0.4) * 1.0,
+        satisfaction: Math.max(75, teamAvgScore + Math.cos(i * 0.3) * 8),
+        resolution: Math.max(70, teamAvgScore + Math.sin(i * 0.5) * 6),
+        tickets: Math.max(2, Math.round(15 + Math.sin(i * 0.6) * 5)),
+      }));
+
+      return NextResponse.json({
+        success: true,
+        data: {
+          view: 'team',
+          teamRankingData,
+          chartData,
+          timeRange,
+        },
+      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const teamAvgScore = teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length;
const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({
period: i + 1,
responseTime: 3.2 + (Math.sin(i * 0.4) * 1.0),
satisfaction: Math.max(75, teamAvgScore + (Math.cos(i * 0.3) * 8)),
resolution: Math.max(70, teamAvgScore + (Math.sin(i * 0.5) * 6)),
tickets: Math.max(2, Math.round(15 + (Math.sin(i * 0.6) * 5)))
}));
return NextResponse.json({
success: true,
data: {
view: 'team',
teamRankingData,
chartData,
timeRange
}
});
}
if (teamRankingData.length === 0) {
return NextResponse.json({
success: true,
data: {
view: 'team',
teamRankingData: [],
chartData: [],
timeRange
}
});
}
const teamAvgScore =
teamRankingData.reduce((sum, agent) => sum + agent.score, 0) / teamRankingData.length;
const chartData = Array.from({ length: chartDataPoints }, (_, i) => ({
period: i + 1,
responseTime: 3.2 + Math.sin(i * 0.4) * 1.0,
satisfaction: Math.max(75, teamAvgScore + Math.cos(i * 0.3) * 8),
resolution: Math.max(70, teamAvgScore + Math.sin(i * 0.5) * 6),
tickets: Math.max(2, Math.round(15 + Math.sin(i * 0.6) * 5)),
}));
return NextResponse.json({
success: true,
data: {
view: 'team',
teamRankingData,
chartData,
timeRange,
},
});
}
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/performance/route.ts around lines 182 to 201,
guard against empty teams to avoid dividing by zero and producing NaN in the
JSON: if teamRankingData is empty, early-return a well-formed response (success:
true) with view: 'team', teamRankingData: [], chartData: [] (or an appropriate
empty chart shape), and timeRange so the UI receives consistent types;
alternatively compute teamAvgScore with a conditional (teamRankingData.length ?
sum/length : 0) before building chartData to prevent NaN.

Comment on lines +44 to +46
generatedDate: appointment.createdAt,
size: `${calculatedSize} MB`,
status: 'completed'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Mismatch with frontend: generatedDate should be generatedAt

ReportGenerator.tsx expects generatedAt; returning generatedDate breaks date rendering.

-          generatedDate: appointment.createdAt,
+          generatedAt: appointment.createdAt,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generatedDate: appointment.createdAt,
size: `${calculatedSize} MB`,
status: 'completed'
generatedAt: appointment.createdAt,
size: `${calculatedSize} MB`,
status: 'completed'
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/reports/route.ts around lines 44 to 46 the
response object uses generatedDate but the frontend expects generatedAt; rename
the property from generatedDate to generatedAt in the returned report object
(and update any related types/interfaces if present) so the frontend can
correctly read and render the date.

Comment on lines +118 to +123
if (action === 'generateReport') {
// Get agent's data for report generation
const agentAppointments = await Appointment.find({
assignedAgent: agentId
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Filter by date range in the DB instead of in-memory

You fetch all agent appointments and then filter in-memory. Push the date filter into the DB query to reduce memory usage and speed up response time.

-      // Get agent's data for report generation
-      const agentAppointments = await Appointment.find({
-        assignedAgent: agentId
-      });
+      // (Moved: fetch periodAppointments after computing date range)
-      // Filter appointments by date range
-      const periodAppointments = agentAppointments.filter(apt => 
-        apt.createdAt >= startDate && apt.createdAt <= now
-      );
+      // Fetch appointments for the computed date range directly from DB
+      const periodAppointments = await Appointment.find({
+        assignedAgent: agentId,
+        createdAt: { $gte: startDate, $lte: endDate }
+      }).select('createdAt status serviceType');

Also applies to: 148-151

Comment on lines +180 to +188
return NextResponse.json({
success: true,
data: {
reportId,
reportData,
downloadUrl: `/api/agent/analytics/reports/download/${reportId}`,
message: 'Report generated successfully'
}
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Follow-up: downloadUrl points to a non-existent route

The response advertises /api/agent/analytics/reports/download/:id but there’s no handler in this PR. Either add a stub route or remove the URL until implemented.

Would you like me to add a minimal download route that returns a mocked file stream or a 501 Not Implemented placeholder?

Run this to check for a download handler:


🏁 Script executed:

#!/bin/bash
rg -n -C2 -g '!**/node_modules/**' -P 'analytics/reports/download'

Length of output: 490


Implement the missing download route or remove its reference

The downloadUrl in your JSON response points to /api/agent/analytics/reports/download/:id, but there’s no handler for this endpoint in the PR. This will lead to 404s at runtime.

Please choose one of the following fixes:

  • Add a stub download route
    Create src/app/api/agent/analytics/reports/download/route.ts with at least a placeholder implementation, e.g.:
    import { NextResponse } from 'next/server';
    
    export async function GET(request: Request, { params }: { params: { id: string } }) {
      // TODO: replace with real file streaming logic
      return NextResponse.json(
        { message: `Download for report ${params.id} is not implemented yet.` },
        { status: 501 }
      );
    }
  • Remove the downloadUrl field
    If you’re not ready to support downloads, omit downloadUrl (and related front-end logic) until the endpoint is implemented.

Please address before merging.

🤖 Prompt for AI Agents
In src/app/api/agent/analytics/reports/route.ts around lines 180-188, the JSON
response includes downloadUrl pointing to
/api/agent/analytics/reports/download/:id but there is no handler for that route
causing 404s; either add a new route handler file at
src/app/api/agent/analytics/reports/download/route.ts that exports an async GET
accepting params.id and returns a placeholder 501 JSON response (or actual
streaming logic later), or remove the downloadUrl field from the response (and
any frontend usage) until the endpoint is implemented.

Comment on lines +70 to +73
const prevPeriodAppointments = await Appointment.find({
assignedAgent: agentId,
createdAt: { $gte: prevStartDate, $lte: prevEndDate }
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix previous-period overlap (double-counting at the boundary)

Using <= prevEndDate where prevEndDate equals startDate includes appointments exactly at startDate in both current and previous windows. Use $lt: startDate for the previous period upper bound.

Apply this diff:

-    const prevPeriodAppointments = await Appointment.find({
-      assignedAgent: agentId,
-      createdAt: { $gte: prevStartDate, $lte: prevEndDate }
-    });
+    const prevPeriodAppointments = await Appointment.find({
+      assignedAgent: agentId,
+      createdAt: { $gte: prevStartDate, $lt: startDate }
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const prevPeriodAppointments = await Appointment.find({
assignedAgent: agentId,
createdAt: { $gte: prevStartDate, $lte: prevEndDate }
});
const prevPeriodAppointments = await Appointment.find({
assignedAgent: agentId,
createdAt: { $gte: prevStartDate, $lt: startDate }
});
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/route.ts around lines 70 to 73, the
previous-period query uses createdAt: { $gte: prevStartDate, $lte: prevEndDate }
which double-counts appointments exactly at the boundary; change the upper bound
to be exclusive by using $lt: startDate (or $lt: prevEndDate if prevEndDate
equals startDate) so the previous period becomes createdAt: { $gte:
prevStartDate, $lt: startDate } to avoid overlap with the current period.

Comment on lines +58 to +60
const periodAppointments = appointments.filter(apt =>
apt.createdAt >= periodStart && apt.createdAt < periodEnd
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Edge case: last bin excludes appointments at exactly "now"

Appointments with createdAt equal to the computed last bin end (which equals "now") are included in the DB query (<= now) but excluded when binning (< periodEnd). Fix by including <= for the last bin only.

Apply this diff within the binning loop:

-      const periodAppointments = appointments.filter(apt => 
-        apt.createdAt >= periodStart && apt.createdAt < periodEnd
-      );
+      const isLastBin = i === dataPoints - 1;
+      const periodAppointments = appointments.filter(apt =>
+        apt.createdAt >= periodStart && (isLastBin ? apt.createdAt <= periodEnd : apt.createdAt < periodEnd)
+      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const periodAppointments = appointments.filter(apt =>
apt.createdAt >= periodStart && apt.createdAt < periodEnd
);
const isLastBin = i === dataPoints - 1;
const periodAppointments = appointments.filter(apt =>
apt.createdAt >= periodStart &&
(isLastBin ? apt.createdAt <= periodEnd : apt.createdAt < periodEnd)
);
🤖 Prompt for AI Agents
In src/app/api/agent/analytics/trends/route.ts around lines 58 to 60, the
binning filter currently excludes appointments whose createdAt equals the final
periodEnd (now) even though the DB query includes <= now; update the filter to
include createdAt <= periodEnd for the last bin only (e.g., detect the last
iteration by index or compare periodEnd to now) while keeping the existing <
periodEnd behavior for earlier bins so appointments at exactly "now" are counted
in the final bin.

Comment on lines +53 to +55
value: calculateChange(pendingAppointments, lastWeekPending),
isPositive: pendingAppointments >= lastWeekPending
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Trend polarity for pending appointments is inverted

An increase in pending items is typically negative. Invert isPositive to reflect improvements when pending decreases.

Apply this diff:

-          value: calculateChange(pendingAppointments, lastWeekPending),
-          isPositive: pendingAppointments >= lastWeekPending
+          value: calculateChange(pendingAppointments, lastWeekPending),
+          isPositive: pendingAppointments <= lastWeekPending
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
value: calculateChange(pendingAppointments, lastWeekPending),
isPositive: pendingAppointments >= lastWeekPending
}
value: calculateChange(pendingAppointments, lastWeekPending),
isPositive: pendingAppointments <= lastWeekPending
🤖 Prompt for AI Agents
In src/app/api/agent/dashboard/route.ts around lines 53 to 55, the trend
polarity for pending appointments is inverted: currently isPositive is true when
pendingAppointments >= lastWeekPending, but a decrease in pending appointments
is an improvement. Change the isPositive expression to reflect improvement on
decrease (e.g., set isPositive to pendingAppointments <= lastWeekPending or
invert the boolean) so that a lower current pending count yields a positive
trend.

Comment on lines +5 to 8
export async function GET() {
try {
await connectDB();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard the debug route behind an environment check

This endpoint exposes a list of admins (emails, roles). It should be disabled in non-development environments.

Apply this diff:

 export async function GET() {
   try {
+    if (process.env.NODE_ENV !== "development") {
+      return NextResponse.json(
+        { success: false, message: "check-admins is disabled outside development" },
+        { status: 403 }
+      );
+    }
     await connectDB();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function GET() {
try {
await connectDB();
export async function GET() {
try {
if (process.env.NODE_ENV !== "development") {
return NextResponse.json(
{ success: false, message: "check-admins is disabled outside development" },
{ status: 403 }
);
}
await connectDB();
🤖 Prompt for AI Agents
In src/app/api/debug/check-admins/route.ts around lines 5 to 8, the debug GET
route currently exposes admin emails/roles in all environments; add an early
environment guard before calling connectDB to disable this endpoint unless
running in development: check process.env.NODE_ENV === 'development' (or a
dedicated env var like DEBUG_ADMIN_ROUTE === 'true'), and if the check fails
immediately return a safe status (404 or 403) without connecting to the DB or
returning data; keep the guard first line in the handler so no sensitive info is
reachable in production.

Comment on lines +831 to +846
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#FFC72C]"></div>
</div>
) : recentReports.length === 0 ? (
<div className="text-center py-12">
<div className="p-4 bg-muted/20 rounded-full w-16 h-16 mx-auto mb-4 flex items-center justify-center">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</svg>
</div>
<h3 className="text-lg font-semibold text-foreground mb-2">No Reports Yet</h3>
<p className="text-muted-foreground">Generate your first report to see it here.</p>
</div>
) : (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

History view: frontend expects generatedAt; ensure backend matches or add fallback

ReportGenerator renders report.generatedAt. With the backend fixed to return generatedAt (see API comment), this will work. If not, add a minimal fallback to support generatedDate to avoid runtime issues.

Run this to verify both sides use generatedAt consistently:

Also applies to: 848-895


🏁 Script executed:

#!/bin/bash
rg -n -C2 -g '!**/node_modules/**' -P '\bgeneratedAt\b|\bgeneratedDate\b'

Length of output: 4908


I’d like to inspect the two places in the API to confirm how generatedDate vs. generatedAt are returned.


🏁 Script executed:

#!/bin/bash
# Show context around the use of generatedDate
rg -nC3 "generatedDate" src/app/api/agent/analytics/reports/route.ts

# Show context around the use of generatedAt
rg -nC3 "generatedAt" src/app/api/agent/analytics/reports/route.ts

Length of output: 657


Unify report timestamp field across API and frontend

The API handler in src/app/api/agent/analytics/reports/route.ts returns generatedDate for the initial history payload (line 44) but uses generatedAt elsewhere (line 169), whereas ReportGenerator.tsx only reads report.generatedAt. This mismatch will cause undefined timestamps and potential runtime errors.

Please address one of the following:

  • Backend fix (preferred):
    • In src/app/api/agent/analytics/reports/route.ts at line 44, change
      - generatedDate: appointment.createdAt,
      + generatedAt:   appointment.createdAt,
  • Frontend fallback:
    • In src/components/agent/analytics/ReportGenerator.tsx, replace
      <span>{formatDate(report.generatedAt)}</span>
      with
      <span>{formatDate(report.generatedAt ?? report.generatedDate)}</span>
🤖 Prompt for AI Agents
In src/components/agent/analytics/ReportGenerator.tsx around lines 831-846 the
UI reads only report.generatedAt which can be undefined because the backend
sometimes returns generatedDate; update the rendering to fall back to
generatedDate when generatedAt is missing (use a nullish-coalescing or
conditional fallback so formatDate is passed report.generatedAt if present
otherwise report.generatedDate), and adjust the report type/props or add a safe
optional check so TypeScript and runtime won't error when one of the fields is
undefined.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants