diff --git a/DASHBOARD_FORMULAS.md b/DASHBOARD_FORMULAS.md new file mode 100644 index 0000000..f409050 --- /dev/null +++ b/DASHBOARD_FORMULAS.md @@ -0,0 +1,653 @@ +# Dashboard Formulas & Calculations Documentation + +This document provides a comprehensive breakdown of all formulas and calculations used in the Dashboard for both **Admin/Management** users and **Regular Users**. + +--- + +## 📊 **1. REQUEST VOLUME STATISTICS** + +### **Admin/Management View** +**Scope:** All requests across the organization + +**Formulas:** +``` +Total Requests = COUNT(*) + WHERE submission_date BETWEEN :start AND :end + AND is_draft = false + AND is_deleted = false + AND submission_date IS NOT NULL + +Approved Requests = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND is_draft = false + +Rejected Requests = COUNT(*) + WHERE status = 'REJECTED' + AND submission_date BETWEEN :start AND :end + AND is_draft = false + +Pending Requests = COUNT(*) + WHERE status IN ('PENDING', 'IN_PROGRESS') + AND is_draft = false + (Note: Includes ALL pending requests regardless of creation date) + +Draft Requests = COUNT(*) + WHERE is_draft = true +``` + +### **Regular User View** +**Scope:** Only requests initiated by the user + +**Formulas:** +``` +Total Requests = COUNT(*) + WHERE submission_date BETWEEN :start AND :end + AND is_draft = false + AND initiator_id = :userId + +Approved Requests = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND initiator_id = :userId + AND is_draft = false + +Rejected Requests = COUNT(*) + WHERE status = 'REJECTED' + AND submission_date BETWEEN :start AND :end + AND initiator_id = :userId + AND is_draft = false + +Pending Requests = COUNT(*) + WHERE status IN ('PENDING', 'IN_PROGRESS') + AND initiator_id = :userId + AND is_draft = false + +Draft Requests = COUNT(*) + WHERE is_draft = true + AND initiator_id = :userId +``` + +--- + +## ⏱️ **2. TAT EFFICIENCY & SLA COMPLIANCE** + +### **Admin/Management View** +**Scope:** All completed requests in date range + +**Formulas:** +``` +Completed Requests = COUNT(*) + WHERE status IN ('APPROVED', 'REJECTED') + AND is_draft = false + AND submission_date IS NOT NULL + AND ( + (closure_date IS NOT NULL AND closure_date BETWEEN :start AND :end) + OR (closure_date IS NULL AND updated_at BETWEEN :start AND :end) + ) + +Breached Requests = COUNT(DISTINCT request_id) + WHERE EXISTS ( + SELECT 1 FROM tat_alerts ta + WHERE ta.request_id = wf.request_id + AND ta.is_breached = true + ) + +Compliant Requests = Total Completed - Breached Requests + +SLA Compliance % = ROUND((Compliant Requests / Total Completed) × 100, 0) + If Total Completed = 0, then Compliance = 0% + +Average Cycle Time (Hours) = ROUND(SUM(cycle_times) / COUNT(cycle_times), 1) + Where cycle_time = calculateElapsedWorkingHours(submission_date, completion_date, priority) + (Respects working hours, weekends, holidays based on priority) + +Average Cycle Time (Days) = ROUND(Average Cycle Time (Hours) / 8, 1) + (Assumes 8 working hours per day) +``` + +### **Regular User View** +**Scope:** Only completed requests initiated by the user + +**Formulas:** +``` +Completed Requests = COUNT(*) + WHERE status IN ('APPROVED', 'REJECTED') + AND is_draft = false + AND submission_date IS NOT NULL + AND initiator_id = :userId + AND ( + (closure_date IS NOT NULL AND closure_date BETWEEN :start AND :end) + OR (closure_date IS NULL AND updated_at BETWEEN :start AND :end) + ) + +Breached Requests = COUNT(DISTINCT request_id) + WHERE initiator_id = :userId + AND EXISTS ( + SELECT 1 FROM tat_alerts ta + WHERE ta.request_id = wf.request_id + AND ta.is_breached = true + ) + +Compliant Requests = Total Completed - Breached Requests + +SLA Compliance % = ROUND((Compliant Requests / Total Completed) × 100, 0) + +Average Cycle Time (Hours) = Same calculation as Admin +Average Cycle Time (Days) = Same calculation as Admin +``` + +**Note:** Breaches are tracked at **approver/level level** but counted at **request level** for SLA compliance. + +--- + +## 👤 **3. APPROVER LOAD STATISTICS** + +### **Admin/Management View** +**Not Applicable** - This metric is user-specific + +### **Regular User View** +**Scope:** User's approval workload + +**Formulas:** +``` +Pending Actions = COUNT(DISTINCT level_id) + WHERE approver_id = :userId + AND status = 'IN_PROGRESS' + AND request.status IN ('PENDING', 'IN_PROGRESS') + AND is_draft = false + AND level_number = request.current_level + (Only counts requests at user's current active level) + +Completed Today = COUNT(*) + WHERE approver_id = :userId + AND status IN ('APPROVED', 'REJECTED') + AND action_date BETWEEN :start AND :end + (Date range start/end for "today") + +Completed This Week = COUNT(*) + WHERE approver_id = :userId + AND status IN ('APPROVED', 'REJECTED') + AND action_date >= start_of_week + AND action_date BETWEEN :start AND :end +``` + +--- + +## 📝 **4. ENGAGEMENT STATISTICS** + +### **Admin/Management View** +**Scope:** All work notes and documents across organization + +**Formulas:** +``` +Work Notes Added = COUNT(*) + FROM work_notes wn + WHERE wn.created_at BETWEEN :start AND :end + +Attachments Uploaded = COUNT(*) + FROM documents d + WHERE d.uploaded_at BETWEEN :start AND :end +``` + +### **Regular User View** +**Scope:** Only from requests initiated by the user + +**Formulas:** +``` +Work Notes Added = COUNT(*) + FROM work_notes wn + WHERE wn.created_at BETWEEN :start AND :end + AND EXISTS ( + SELECT 1 FROM workflow_requests wf + WHERE wf.request_id = wn.request_id + AND wf.initiator_id = :userId + AND wf.is_draft = false + ) + +Attachments Uploaded = COUNT(*) + FROM documents d + WHERE d.uploaded_at BETWEEN :start AND :end + AND EXISTS ( + SELECT 1 FROM workflow_requests wf + WHERE wf.request_id = d.request_id + AND wf.initiator_id = :userId + AND wf.is_draft = false + ) +``` + +--- + +## 🤖 **5. AI INSIGHTS** + +### **Admin/Management View** +**Scope:** All approved requests with conclusion remarks + +**Formulas:** +``` +Total with Conclusion = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND conclusion_remark IS NOT NULL + AND is_draft = false + +AI Generated Count = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND ai_generated_conclusion IS NOT NULL + AND ai_generated_conclusion != '' + AND is_draft = false + +Manual Count = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND (ai_generated_conclusion IS NULL OR ai_generated_conclusion = '') + AND is_draft = false + +AI Adoption % = ROUND((AI Generated Count / Total with Conclusion) × 100, 0) + +Average Remark Length = ROUND(AVG(LENGTH(conclusion_remark)), 0) + WHERE status = 'APPROVED' + AND conclusion_remark IS NOT NULL +``` + +### **Regular User View** +**Scope:** Only approved requests initiated by the user + +**Formulas:** +``` +Total with Conclusion = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND conclusion_remark IS NOT NULL + AND initiator_id = :userId + AND is_draft = false + +AI Generated Count = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND ai_generated_conclusion IS NOT NULL + AND ai_generated_conclusion != '' + AND initiator_id = :userId + AND is_draft = false + +Manual Count = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND (ai_generated_conclusion IS NULL OR ai_generated_conclusion = '') + AND initiator_id = :userId + AND is_draft = false + +AI Adoption % = ROUND((AI Generated Count / Total with Conclusion) × 100, 0) + +Average Remark Length = Same calculation as Admin (filtered by initiator_id) +``` + +--- + +## 📈 **6. SUCCESS RATE (Regular Users Only)** + +**Formula:** +``` +Success Rate % = ROUND((Approved Requests / Total Requests) × 100, 0) + If Total Requests = 0, then Success Rate = 0% + +Where: + Approved Requests = From Request Volume Statistics + Total Requests = From Request Volume Statistics +``` + +--- + +## 🏢 **7. DEPARTMENT STATISTICS (Admin Only)** + +**Scope:** All requests grouped by initiator's department + +**Formulas:** +``` +Department Stats = GROUP BY initiator.department + +Total Requests per Dept = COUNT(*) + WHERE submission_date BETWEEN :start AND :end + AND is_draft = false + GROUP BY initiator.department + +Approved per Dept = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND is_draft = false + GROUP BY initiator.department + +Rejected per Dept = COUNT(*) + WHERE status = 'REJECTED' + AND submission_date BETWEEN :start AND :end + AND is_draft = false + GROUP BY initiator.department + +In Progress per Dept = COUNT(*) + WHERE status IN ('PENDING', 'IN_PROGRESS') + AND submission_date BETWEEN :start AND :end + AND is_draft = false + GROUP BY initiator.department + +Approval Rate per Dept = ROUND((Approved / Total Requests) × 100, 0) + If Total Requests = 0, then Approval Rate = 0% +``` + +**Note:** Limited to top 10 departments by total requests. + +--- + +## 🎯 **8. PRIORITY DISTRIBUTION (Admin Only)** + +**Scope:** All requests grouped by priority (EXPRESS vs STANDARD) + +**Formulas:** +``` +Total Count per Priority = COUNT(*) + WHERE submission_date BETWEEN :start AND :end + AND is_draft = false + GROUP BY priority + +Approved Count per Priority = COUNT(*) + WHERE status = 'APPROVED' + AND submission_date BETWEEN :start AND :end + AND is_draft = false + GROUP BY priority + +Breached Count per Priority = COUNT(DISTINCT request_id) + WHERE EXISTS ( + SELECT 1 FROM tat_alerts ta + WHERE ta.request_id = wf.request_id + AND ta.is_breached = true + ) + GROUP BY priority + +Average Cycle Time per Priority = ROUND(SUM(cycle_times) / COUNT(cycle_times), 1) + Where cycle_time = calculateElapsedWorkingHours(submission_date, completion_date, priority) + Only for COMPLETED requests (status IN ('APPROVED', 'REJECTED')) + GROUP BY priority + +Compliance Rate per Priority = ROUND(((Total Count - Breached Count) / Total Count) × 100, 0) + If Total Count = 0, then Compliance Rate = 0% +``` + +--- + +## 👥 **9. APPROVER PERFORMANCE (Admin Only)** + +**Scope:** All approvers who completed approvals in date range + +**Formulas:** +``` +Total Approved per Approver = COUNT(DISTINCT level_id) + WHERE approver_id = :approverId + AND action_date BETWEEN :start AND :end + AND status IN ('APPROVED', 'REJECTED') + AND action_date IS NOT NULL + AND level_start_time IS NOT NULL + AND tat_hours > 0 + AND elapsed_hours > 0 + +Within TAT Count = COUNT(DISTINCT level_id) + WHERE approver_id = :approverId + AND action_date BETWEEN :start AND :end + AND status IN ('APPROVED', 'REJECTED') + AND elapsed_hours IS NOT NULL + AND elapsed_hours > 0 + AND ( + elapsed_hours < tat_hours + OR (elapsed_hours <= tat_hours AND (tat_breached IS NULL OR tat_breached = false)) + OR (tat_breached IS NOT NULL AND tat_breached = false) + ) + +Breached Count = COUNT(DISTINCT level_id) + WHERE approver_id = :approverId + AND action_date BETWEEN :start AND :end + AND status IN ('APPROVED', 'REJECTED') + AND elapsed_hours IS NOT NULL + AND elapsed_hours > 0 + AND ( + elapsed_hours > tat_hours + OR (tat_breached IS NOT NULL AND tat_breached = true) + ) + +TAT Compliance % = ROUND((Within TAT Count / Total Approved) × 100, 0) + If Total Approved = 0, then TAT Compliance = 0% + +Average Response Hours = ROUND(AVG(elapsed_hours), 1) + WHERE approver_id = :approverId + AND action_date BETWEEN :start AND :end + AND status IN ('APPROVED', 'REJECTED') + AND elapsed_hours IS NOT NULL + AND elapsed_hours > 0 + +Pending Count = COUNT(DISTINCT level_id) + WHERE approver_id = :approverId + AND status IN ('PENDING', 'IN_PROGRESS') + AND request.status IN ('PENDING', 'IN_PROGRESS') + AND is_draft = false + AND level_number = request.current_level + (Only current active level for each request) +``` + +**Sorting Order:** +1. TAT Compliance % (DESC - highest first) +2. Average Response Hours (ASC - fastest first) +3. Total Approved (DESC - most approvals first) + +--- + +## 🤖 **10. AI REMARK UTILIZATION (Admin Only)** + +**Scope:** All conclusion remarks generated in date range + +**Formulas:** +``` +Total Usage = COUNT(*) + FROM conclusion_remarks cr + WHERE cr.generated_at BETWEEN :start AND :end + +Total Edits = COUNT(*) + FROM conclusion_remarks cr + WHERE cr.generated_at BETWEEN :start AND :end + AND cr.is_edited = true + +Edit Rate % = ROUND((Total Edits / Total Usage) × 100, 0) + If Total Usage = 0, then Edit Rate = 0% + +Monthly Trends (Last 7 Months): + AI Usage = COUNT(*) + WHERE generated_at >= NOW() - INTERVAL '7 months' + GROUP BY DATE_TRUNC('month', generated_at) + + Manual Edits = COUNT(*) + WHERE generated_at >= NOW() - INTERVAL '7 months' + AND is_edited = true + GROUP BY DATE_TRUNC('month', generated_at) +``` + +--- + +## 📅 **11. DATE RANGE CALCULATIONS** + +**Date Range Parsing:** +``` +Today: + start = start_of_day(today) + end = end_of_day(today) + +This Week: + start = start_of_week(today) + end = end_of_week(today) + +This Month: + start = start_of_month(today) + end = end_of_month(today) + +This Quarter: + start = start_of_quarter(today) + end = end_of_quarter(today) + +This Year: + start = start_of_year(today) + end = end_of_year(today) + +Custom Range: + start = start_of_day(custom_start_date) + end = end_of_day(custom_end_date) + (Capped at current date if future date provided) + +Default (if not specified): + start = 30 days ago (start of day) + end = today (end of day) +``` + +--- + +## 🔍 **12. CRITICAL ALERTS** + +### **Admin/Management View** +**Scope:** All requests with critical TAT status + +**Formulas:** +``` +Critical Requests = Requests where: + - TAT percentage used >= 80% (approaching deadline) + - OR TAT percentage used >= 100% (breached) + - OR has breach alerts (is_breached = true in tat_alerts) + +Breached Count = COUNT(*) + WHERE breachCount > 0 + (From critical requests) + +Warning Count = COUNT(*) + WHERE breachCount = 0 + AND TAT percentage >= 80% + (From critical requests) +``` + +### **Regular User View** +**Scope:** Only requests initiated by the user + +**Formulas:** +``` +Critical Requests = Same logic as Admin, filtered by initiator_id = :userId + +Breached Count = Same calculation as Admin (filtered by user) +Warning Count = Same calculation as Admin (filtered by user) +``` + +--- + +## 📊 **13. UPCOMING DEADLINES** + +**Scope:** Requests with active levels approaching TAT deadline + +**Formulas:** +``` +Upcoming Deadlines = Requests where: + - Current level is active (status IN ('PENDING', 'IN_PROGRESS')) + - remainingHours > 0 (not yet breached) + - tatPercentageUsed < 100 (not yet breached) + +TAT Percentage Used = ROUND((elapsedHours / tatHours) × 100, 0) + Where elapsedHours = calculateElapsedWorkingHours(level_start_time, current_time, priority) + +Remaining Hours = MAX(0, tatHours - elapsedHours) + +Elapsed Hours = calculateElapsedWorkingHours(level_start_time, current_time, priority) + (Respects working hours, weekends, holidays based on priority) +``` + +**Note:** Only shows requests that are NOT yet breached (remainingHours > 0 and tatPercentage < 100). + +--- + +## 📝 **14. RECENT ACTIVITY** + +### **Admin/Management View** +**Scope:** All workflow activities across organization + +**Formulas:** +``` +Recent Activity = All activities from workflow_requests + ORDER BY activity.created_at DESC + (No user filter) +``` + +### **Regular User View** +**Scope:** Activities from user's requests or where user is a participant + +**Formulas:** +``` +Recent Activity = Activities where: + - request.initiator_id = :userId + - OR user is a participant in the request + ORDER BY activity.created_at DESC +``` + +--- + +## 🔑 **KEY DIFFERENCES: ADMIN vs REGULAR USER** + +| Metric | Admin/Management | Regular User | +|--------|------------------|--------------| +| **Request Volume** | All organization requests | Only user-initiated requests | +| **TAT Efficiency** | All completed requests | Only user-initiated completed requests | +| **Approver Load** | N/A | User's own approval workload | +| **Engagement** | All work notes/documents | Only from user's requests | +| **AI Insights** | All approved requests | Only user's approved requests | +| **Department Stats** | ✅ Available | ❌ Not available | +| **Priority Distribution** | ✅ Available | ❌ Not available | +| **Approver Performance** | ✅ Available | ❌ Not available | +| **AI Remark Utilization** | ✅ Available | ❌ Not available | +| **Success Rate** | ❌ Not shown | ✅ Available | + +--- + +## 📌 **IMPORTANT NOTES** + +1. **Date Filtering:** + - Most metrics use `submission_date` (when request was submitted), not `created_at` + - Completed requests use `closure_date` or `updated_at` for completion date + - Pending requests are counted regardless of creation date + +2. **Working Hours Calculation:** + - Cycle time uses `calculateElapsedWorkingHours()` which respects: + - Working hours (9 AM - 6 PM) + - Weekends (for STANDARD priority) + - Holidays (configured in system) + - Priority type (EXPRESS vs STANDARD) + +3. **Breach Tracking:** + - Breaches are tracked at **approver/level level** (each level has its own TAT) + - But SLA compliance counts at **request level** (if any level breaches, entire request is non-compliant) + +4. **Rounding:** + - Percentages: Rounded to nearest integer (0 decimal places) + - Hours: Rounded to 1 decimal place + - Days: Rounded to 1 decimal place + +5. **Null Handling:** + - All COUNT operations handle NULL values + - Division operations use NULLIF to prevent division by zero + - Default values are 0 if no data exists + +--- + +## 🔄 **DATA REFRESH** + +All calculations are performed in real-time when: +- Dashboard is loaded +- Date range filter is changed +- Refresh button is clicked +- Custom date range is applied + +Data is fetched in parallel for optimal performance. + +--- + +**Last Updated:** Based on codebase as of current date +**Version:** 1.0 + diff --git a/src/components/workflow/ApprovalWorkflow/ApprovalStepCard.tsx b/src/components/workflow/ApprovalWorkflow/ApprovalStepCard.tsx index 025613a..22dcb0b 100644 --- a/src/components/workflow/ApprovalWorkflow/ApprovalStepCard.tsx +++ b/src/components/workflow/ApprovalWorkflow/ApprovalStepCard.tsx @@ -36,6 +36,20 @@ interface ApprovalStepCardProps { testId?: string; } +// Helper function to format working hours as days (8 hours = 1 working day) +const formatWorkingHours = (hours: number): string => { + const WORKING_HOURS_PER_DAY = 8; + if (hours < WORKING_HOURS_PER_DAY) { + return `${hours.toFixed(1)}h`; + } + const days = Math.floor(hours / WORKING_HOURS_PER_DAY); + const remainingHours = hours % WORKING_HOURS_PER_DAY; + if (remainingHours > 0) { + return `${days}d ${remainingHours.toFixed(1)}h`; + } + return `${days}d`; +}; + const getStepIcon = (status: string, isSkipped?: boolean) => { if (isSkipped) return ; @@ -143,7 +157,7 @@ export function ApprovalStepCard({ )} {isCompleted && actualHours && ( - {actualHours.toFixed(1)} hours + {formatWorkingHours(actualHours)} )} @@ -167,7 +181,7 @@ export function ApprovalStepCard({
Completed in: - {actualHours.toFixed(1)} hours + {formatWorkingHours(actualHours)}
{/* Progress Bar for Completed - Shows actual time used vs TAT allocated */} diff --git a/src/hooks/useRequestDetails.ts b/src/hooks/useRequestDetails.ts index 6288179..7310dfe 100644 --- a/src/hooks/useRequestDetails.ts +++ b/src/hooks/useRequestDetails.ts @@ -335,8 +335,10 @@ export function useRequestDetails( elapsedHours: Number(a.elapsedHours || 0), remainingHours: Number(a.remainingHours || 0), tatPercentageUsed: Number(a.tatPercentageUsed || 0), - actualHours: a.levelEndTime && a.levelStartTime - ? Math.max(0, (new Date(a.levelEndTime).getTime() - new Date(a.levelStartTime).getTime()) / (1000 * 60 * 60)) + // Use backend-calculated elapsedHours (working hours) for completed approvals + // Backend already calculates this correctly using calculateElapsedWorkingHours + actualHours: a.elapsedHours !== undefined && a.elapsedHours !== null + ? Number(a.elapsedHours) : undefined, comment: a.comments || undefined, timestamp: a.actionDate || undefined, diff --git a/src/pages/Dashboard/Dashboard.tsx b/src/pages/Dashboard/Dashboard.tsx index f2d83d4..7345680 100644 --- a/src/pages/Dashboard/Dashboard.tsx +++ b/src/pages/Dashboard/Dashboard.tsx @@ -56,6 +56,7 @@ export function Dashboard({ onNavigate, onNewRequest }: DashboardProps) { const [approverPerformance, setApproverPerformance] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); + const [exportingDeptStats, setExportingDeptStats] = useState(false); // Filter states const [dateRange, setDateRange] = useState('month'); @@ -291,6 +292,48 @@ export function Dashboard({ onNavigate, onNewRequest }: DashboardProps) { } }; + // Export Department Stats to CSV + const exportDepartmentStatsToCSV = async () => { + try { + setExportingDeptStats(true); + + // Get all department stats (not just the displayed ones) + const allDeptStats = await dashboardService.getDepartmentStats(dateRange, customStartDate, customEndDate); + + const csvRows = [ + ['Department', 'Total Requests', 'Approved', 'Rejected', 'In Progress', 'Approval Rate (%)'].join(',') + ]; + + allDeptStats.forEach((dept: any) => { + const row = [ + `"${(dept.department || 'Unknown').replace(/"/g, '""')}"`, + dept.totalRequests || 0, + dept.approved || 0, + dept.rejected || 0, + dept.inProgress || 0, + dept.approvalRate || 0 + ]; + csvRows.push(row.join(',')); + }); + + const csvContent = csvRows.join('\n'); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', `department-workflow-summary-${new Date().toISOString().split('T')[0]}.csv`); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error: any) { + console.error('Failed to export department stats:', error); + alert('Failed to export department statistics. Please try again.'); + } finally { + setExportingDeptStats(false); + } + }; + // Helper function for activity pagination page numbers const getActivityPageNumbers = () => { const pages = []; @@ -540,13 +583,6 @@ export function Dashboard({ onNavigate, onNewRequest }: DashboardProps) { - {/* Export Button */} - {isAdmin && ( - - )} @@ -1022,9 +1058,17 @@ export function Dashboard({ onNavigate, onNewRequest }: DashboardProps) { - @@ -1333,7 +1377,7 @@ export function Dashboard({ onNavigate, onNewRequest }: DashboardProps) {
- +
Upcoming Deadlines diff --git a/src/pages/DetailedReports/DetailedReports.tsx b/src/pages/DetailedReports/DetailedReports.tsx index 19fd1fb..e066e37 100644 --- a/src/pages/DetailedReports/DetailedReports.tsx +++ b/src/pages/DetailedReports/DetailedReports.tsx @@ -98,12 +98,14 @@ export function DetailedReports({ onBack }: DetailedReportsProps) { return diffDays; }; - // Helper function to format TAT hours + // Helper function to format TAT hours (working hours to working days) + // Backend returns working hours, so we divide by 8 (working hours per day) not 24 const formatTAT = (hours: number | null | undefined): string => { if (!hours && hours !== 0) return 'N/A'; - if (hours < 24) return `${hours.toFixed(1)}h`; - const days = Math.floor(hours / 24); - const remainingHours = hours % 24; + const WORKING_HOURS_PER_DAY = 8; + if (hours < WORKING_HOURS_PER_DAY) return `${hours.toFixed(1)}h`; + const days = Math.floor(hours / WORKING_HOURS_PER_DAY); + const remainingHours = hours % WORKING_HOURS_PER_DAY; return remainingHours > 0 ? `${days}d ${remainingHours.toFixed(1)}h` : `${days}d`; };