diff --git a/SLA_TRACKING_GUIDE.md b/SLA_TRACKING_GUIDE.md new file mode 100644 index 0000000..53caabd --- /dev/null +++ b/SLA_TRACKING_GUIDE.md @@ -0,0 +1,183 @@ +# SLA Tracking with Working Hours - Implementation Guide + +## Overview + +The SLA tracking system automatically **pauses during non-working hours** and **resumes during working hours**, ensuring accurate TAT (Turnaround Time) calculations. + +## 🎯 Features + +βœ… **Automatic Pause/Resume** - Stops counting during: +- Weekends (Saturday & Sunday) +- Non-working hours (before 9 AM, after 6 PM) +- Holidays (from database) + +βœ… **Real-Time Updates** - Progress updates every minute +βœ… **Visual Indicators** - Shows when paused vs active +βœ… **Working Hours Display** - Shows elapsed and remaining in working hours (e.g., "2d 3h") +βœ… **Next Resume Time** - Shows when tracking will resume during paused state + +## πŸ“ Components Created + +### 1. **Utility: `slaTracker.ts`** +Core calculation functions: +- `isWorkingTime()` - Check if current time is working hours +- `calculateElapsedWorkingHours()` - Count only working hours +- `calculateRemainingWorkingHours()` - Working hours until deadline +- `getSLAStatus()` - Complete SLA status with pause/resume info +- `formatWorkingHours()` - Format hours as "2d 3h" + +### 2. **Hook: `useSLATracking.ts`** +React hook for real-time tracking: +```typescript +const slaStatus = useSLATracking(startDate, deadline); +// Returns: { progress, elapsedHours, remainingHours, isPaused, statusText, ... } +``` + +### 3. **Component: `SLATracker.tsx`** +Visual component with pause/resume indicators: +```tsx + +``` + +## πŸš€ Usage Examples + +### In MyRequests Page (Already Integrated) +```tsx +{request.createdAt && request.dueDate && + request.status !== 'approved' && request.status !== 'rejected' && ( + +)} +``` + +### In RequestDetail Page +Replace the existing SLA progress bar with: +```tsx +import { SLATracker } from '@/components/sla/SLATracker'; + +// In the SLA Progress section: + +``` + +### In OpenRequests Page +```tsx +{request.createdAt && request.dueDate && ( + +)} +``` + +## 🎨 Visual States + +### Active (During Working Hours) +``` +SLA Progress [▢️ Active] [On track] +β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 45.2% +Elapsed: 1d 4h Remaining: 1d 4h +``` + +### Paused (Outside Working Hours) +``` +SLA Progress [⏸️ Paused] [On track] +β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβΈοΈβ–‘β–‘β–‘β–‘β–‘β–‘ 45.2% +Elapsed: 1d 4h Remaining: 1d 4h +⚠️ Resumes in 14h 30m +``` + +### Critical (>75%) +``` +SLA Progress [▢️ Active] [SLA critical] +β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘ 87.5% +Elapsed: 6d 6h Remaining: 1d 2h +``` + +### Breached (100%) +``` +SLA Progress [⏸️ Paused] [SLA breached] +β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 100.0% +Elapsed: 8d 0h Remaining: 0h +⚠️ Resumes in 2h 15m +``` + +## βš™οΈ Configuration + +Working hours are defined in `slaTracker.ts`: +```typescript +const WORK_START_HOUR = 9; // 9 AM +const WORK_END_HOUR = 18; // 6 PM +const WORK_START_DAY = 1; // Monday +const WORK_END_DAY = 5; // Friday +``` + +To change these, update the constants in the utility file. + +## πŸ”„ How It Works + +### Backend (Already Implemented) +1. βœ… Calculates deadlines using `addWorkingHours()` (skips weekends, holidays, non-work hours) +2. βœ… Stores calculated deadline in database +3. βœ… TAT scheduler triggers notifications at 50%, 75%, 100% (accounting for working hours) + +### Frontend (New Implementation) +1. **Receives** pre-calculated deadline from backend +2. **Calculates** real-time elapsed working hours from start to now +3. **Displays** accurate progress that only counts working time +4. **Shows** pause indicator when outside working hours +5. **Updates** every minute automatically + +### Example Flow: + +**Friday 4:00 PM** (within working hours) +- SLA Progress: [▢️ Active] 25% +- Shows real-time progress + +**Friday 6:01 PM** (after hours) +- SLA Progress: [⏸️ Paused] 25% +- Shows "Resumes in 15h" (Monday 9 AM) + +**Monday 9:00 AM** (work resumes) +- SLA Progress: [▢️ Active] 25% +- Continues from where it left off + +**Monday 10:00 AM** (1 working hour later) +- SLA Progress: [▢️ Active] 30% +- Progress updates only during working hours + +## 🎯 Benefits + +1. **Accurate SLA Tracking** - Only counts actual working time +2. **User Transparency** - Users see when SLA is paused +3. **Realistic Deadlines** - No false urgency during weekends +4. **Aligned with Backend** - Frontend display matches backend calculations +5. **Real-Time Updates** - Live progress without page refresh + +## πŸ§ͺ Testing + +Test different scenarios: +1. **During working hours** β†’ Should show "Active" badge +2. **After 6 PM** β†’ Should show "Paused" and resume time +3. **Weekends** β†’ Should show "Paused" and Monday 9 AM resume +4. **Progress calculation** β†’ Should only count working hours + +## πŸ“ Notes + +- The frontend assumes the backend has already calculated the correct deadline +- Progress bars update every 60 seconds +- Paused state is visual only - actual TAT calculations are on backend +- For holidays, consider integrating with backend holiday API in future enhancement + diff --git a/SYSTEM_CONFIGURATION.md b/SYSTEM_CONFIGURATION.md new file mode 100644 index 0000000..57c2455 --- /dev/null +++ b/SYSTEM_CONFIGURATION.md @@ -0,0 +1,308 @@ +# System Configuration - Frontend Integration Guide + +## πŸ“‹ Overview + +The Royal Enfield Workflow Management System now uses **centralized, backend-driven configuration**. All system settings are fetched from the backend API and cached on the frontend. + +## 🚫 **NO MORE HARDCODED VALUES!** + +### ❌ Before (Hardcoded): +```typescript +const MAX_MESSAGE_LENGTH = 2000; +const WORK_START_HOUR = 9; +const MAX_APPROVAL_LEVELS = 10; +``` + +### βœ… After (Backend-Driven): +```typescript +import { configService, getWorkNotesConfig } from '@/services/configService'; + +const config = await getWorkNotesConfig(); +const maxLength = config.maxMessageLength; // From backend +``` + +--- + +## 🎯 How to Use Configuration + +### **Method 1: Full Configuration Object** + +```typescript +import { configService } from '@/services/configService'; + +// In component +useEffect(() => { + const loadConfig = async () => { + const config = await configService.getConfig(); + console.log('Max file size:', config.upload.maxFileSizeMB); + console.log('Working hours:', config.workingHours); + }; + loadConfig(); +}, []); +``` + +### **Method 2: Helper Functions** + +```typescript +import { + getWorkingHours, + getTATThresholds, + getUploadLimits, + getWorkNotesConfig, + getFeatureFlags +} from '@/services/configService'; + +// Get specific configuration +const workingHours = await getWorkingHours(); +const tatThresholds = await getTATThresholds(); +const uploadLimits = await getUploadLimits(); +``` + +### **Method 3: React Hook (Recommended)** + +Create a custom hook: +```typescript +// src/hooks/useSystemConfig.ts +import { useState, useEffect } from 'react'; +import { configService, SystemConfig } from '@/services/configService'; + +export function useSystemConfig() { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const loadConfig = async () => { + const cfg = await configService.getConfig(); + setConfig(cfg); + setLoading(false); + }; + loadConfig(); + }, []); + + return { config, loading }; +} + +// Usage in component: +function MyComponent() { + const { config, loading } = useSystemConfig(); + + if (loading) return
Loading...
; + + return ( +
+ Max file size: {config.upload.maxFileSizeMB} MB +
+ ); +} +``` + +--- + +## πŸ”„ Configuration Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Backend (.env) β”‚ +β”‚ Environment β”‚ +β”‚ Variables β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ system.config.tsβ”‚ +β”‚ (Centralized) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”œβ”€β”€β”€β”€β”€β–Ί tat.config.ts (TAT settings) + β”œβ”€β”€β”€β”€β”€β–Ί tatTimeUtils.ts (Uses working hours) + └─────► config.routes.ts (API endpoint) + β”‚ + β–Ό + GET /api/v1/config + β”‚ + β–Ό + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Frontend configService β”‚ + β”‚ (Cached in memory) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”œβ”€β”€β”€β”€β”€β–Ί Components (via hook) + β”œβ”€β”€β”€β”€β”€β–Ί Utils (slaTracker) + └─────► Services +``` + +--- + +## πŸ“Š Configuration Values + +### **Working Hours** +```typescript +const workingHours = await getWorkingHours(); +// { +// START_HOUR: 9, +// END_HOUR: 18, +// START_DAY: 1, // Monday +// END_DAY: 5, // Friday +// TIMEZONE: 'Asia/Kolkata' +// } +``` + +### **TAT Thresholds** +```typescript +const thresholds = await getTATThresholds(); +// { +// warning: 50, // 50% - First reminder +// critical: 75, // 75% - Urgent reminder +// breach: 100 // 100% - Breach alert +// } +``` + +### **Upload Limits** +```typescript +const limits = await getUploadLimits(); +// { +// maxFileSizeMB: 10, +// allowedFileTypes: ['pdf', 'doc', ...], +// maxFilesPerRequest: 10 +// } +``` + +### **Feature Flags** +```typescript +const features = await getFeatureFlags(); +// { +// ENABLE_AI_CONCLUSION: true, +// ENABLE_TEMPLATES: false, +// ENABLE_ANALYTICS: true, +// ENABLE_EXPORT: true +// } +``` + +--- + +## 🎨 Example Integrations + +### **File Upload Component** +```typescript +import { getUploadLimits } from '@/services/configService'; + +function FileUpload() { + const [maxSize, setMaxSize] = useState(10); + + useEffect(() => { + const loadLimits = async () => { + const limits = await getUploadLimits(); + setMaxSize(limits.maxFileSizeMB); + }; + loadLimits(); + }, []); + + return ( + + ); +} +``` + +### **Work Notes Message Input** +```typescript +import { getWorkNotesConfig } from '@/services/configService'; + +function MessageInput() { + const [maxLength, setMaxLength] = useState(2000); + + useEffect(() => { + const loadConfig = async () => { + const config = await getWorkNotesConfig(); + setMaxLength(config.maxMessageLength); + }; + loadConfig(); + }, []); + + return ( +