import { format, formatDistanceToNow, differenceInDays, parseISO } from 'date-fns'; import { AssetRequest, RequestStatus, isValidStatusTransition } from '@/types/asset-request'; // ============================================================================ // DATE FORMATTING HELPERS // ============================================================================ /** * Format a date string for display in the UI * @param dateString - ISO 8601 date string * @param formatString - Optional format string (default: 'MMM dd, yyyy') * @returns Formatted date string */ export function formatRequestDate( dateString: string | null, formatString: string = 'MMM dd, yyyy' ): string { if (!dateString) return 'N/A'; try { const date = parseISO(dateString); return format(date, formatString); } catch (error) { console.error('Error formatting date:', error); return 'Invalid date'; } } /** * Format a date string with time for display * @param dateString - ISO 8601 date string * @returns Formatted date and time string */ export function formatRequestDateTime(dateString: string | null): string { if (!dateString) return 'N/A'; try { const date = parseISO(dateString); return format(date, 'MMM dd, yyyy h:mm a'); } catch (error) { console.error('Error formatting date time:', error); return 'Invalid date'; } } /** * Format a date as relative time (e.g., "2 days ago") * @param dateString - ISO 8601 date string * @returns Relative time string */ export function formatRelativeTime(dateString: string | null): string { if (!dateString) return 'N/A'; try { const date = parseISO(dateString); return formatDistanceToNow(date, { addSuffix: true }); } catch (error) { console.error('Error formatting relative time:', error); return 'Invalid date'; } } /** * Calculate the age of a request in days * @param submittedDate - ISO 8601 date string of submission * @returns Number of days since submission */ export function calculateRequestAge(submittedDate: string): number { try { const date = parseISO(submittedDate); return differenceInDays(new Date(), date); } catch (error) { console.error('Error calculating request age:', error); return 0; } } /** * Get a human-readable age description for a request * @param submittedDate - ISO 8601 date string of submission * @returns Age description (e.g., "2 days old", "Just submitted") */ export function getRequestAgeDescription(submittedDate: string): string { const age = calculateRequestAge(submittedDate); if (age === 0) return 'Just submitted'; if (age === 1) return '1 day old'; if (age < 7) return `${age} days old`; if (age < 14) return '1 week old'; if (age < 30) return `${Math.floor(age / 7)} weeks old`; if (age < 60) return '1 month old'; return `${Math.floor(age / 30)} months old`; } // ============================================================================ // STATUS TRANSITION HELPERS // ============================================================================ /** * Validate if a status transition is allowed * @param currentStatus - Current request status * @param newStatus - Desired new status * @returns Validation result with error message if invalid */ export function validateStatusTransition( currentStatus: RequestStatus, newStatus: RequestStatus ): { valid: boolean; error?: string } { if (currentStatus === newStatus) { return { valid: false, error: 'Request is already in this status' }; } if (!isValidStatusTransition(currentStatus, newStatus)) { return { valid: false, error: `Cannot transition from ${currentStatus} to ${newStatus}` }; } return { valid: true }; } /** * Get allowed next statuses for a given current status * @param currentStatus - Current request status * @returns Array of allowed next statuses */ export function getAllowedNextStatuses(currentStatus: RequestStatus): RequestStatus[] { const transitions: Record = { pending: ['approved', 'rejected'], approved: ['assigned'], rejected: [], assigned: [] }; return transitions[currentStatus] || []; } /** * Check if a request can be modified (only pending requests can be modified) * @param status - Request status * @returns True if request can be modified */ export function canModifyRequest(status: RequestStatus): boolean { return status === 'pending'; } /** * Check if a request can be cancelled (only pending requests can be cancelled) * @param status - Request status * @returns True if request can be cancelled */ export function canCancelRequest(status: RequestStatus): boolean { return status === 'pending'; } // ============================================================================ // SORTING HELPERS // ============================================================================ /** * Sort requests by submission date (newest first) */ export function sortByDateDesc(a: AssetRequest, b: AssetRequest): number { return new Date(b.submittedDate).getTime() - new Date(a.submittedDate).getTime(); } /** * Sort requests by submission date (oldest first) */ export function sortByDateAsc(a: AssetRequest, b: AssetRequest): number { return new Date(a.submittedDate).getTime() - new Date(b.submittedDate).getTime(); } /** * Sort requests by employee name (A-Z) */ export function sortByEmployeeName(a: AssetRequest, b: AssetRequest): number { return a.employeeName.localeCompare(b.employeeName); } /** * Sort requests by status (pending first, then approved, rejected, assigned) */ export function sortByStatus(a: AssetRequest, b: AssetRequest): number { const statusOrder: Record = { pending: 1, approved: 2, rejected: 3, assigned: 4 }; return statusOrder[a.status] - statusOrder[b.status]; } /** * Sort requests by request ID (newest first) */ export function sortByRequestId(a: AssetRequest, b: AssetRequest): number { return b.requestID - a.requestID; } // ============================================================================ // FILTERING HELPERS // ============================================================================ /** * Filter requests by status */ export function filterByStatus( requests: AssetRequest[], status: RequestStatus ): AssetRequest[] { return requests.filter(req => req.status === status); } /** * Filter requests by employee ID */ export function filterByEmployeeId( requests: AssetRequest[], employeeId: number ): AssetRequest[] { return requests.filter(req => req.employeeID === employeeId); } /** * Filter requests by date range */ export function filterByDateRange( requests: AssetRequest[], startDate: string, endDate: string ): AssetRequest[] { const start = parseISO(startDate); const end = parseISO(endDate); return requests.filter(req => { const reqDate = parseISO(req.submittedDate); return reqDate >= start && reqDate <= end; }); } /** * Filter requests by search term (searches in justification, asset types, and comments) */ export function filterBySearchTerm( requests: AssetRequest[], searchTerm: string ): AssetRequest[] { if (!searchTerm.trim()) return requests; const term = searchTerm.toLowerCase(); return requests.filter(req => req.requestID.toString().includes(term) || req.employeeName.toLowerCase().includes(term) || req.justification.toLowerCase().includes(term) || req.requestedAssets.some(asset => asset.assetType.toLowerCase().includes(term) || asset.specifications.toLowerCase().includes(term) ) || (req.adminComments && req.adminComments.toLowerCase().includes(term)) ); } /** * Filter requests by asset type */ export function filterByAssetType( requests: AssetRequest[], assetType: string ): AssetRequest[] { return requests.filter(req => req.requestedAssets.some(asset => asset.assetType === assetType) ); } /** * Filter old pending requests (older than specified days) */ export function filterOldPendingRequests( requests: AssetRequest[], daysThreshold: number = 30 ): AssetRequest[] { return requests.filter(req => { if (req.status !== 'pending') return false; const age = calculateRequestAge(req.submittedDate); return age >= daysThreshold; }); } // ============================================================================ // AGGREGATION HELPERS // ============================================================================ /** * Get request counts by status */ export function getRequestCountsByStatus( requests: AssetRequest[] ): Record { return { pending: requests.filter(r => r.status === 'pending').length, approved: requests.filter(r => r.status === 'approved').length, rejected: requests.filter(r => r.status === 'rejected').length, assigned: requests.filter(r => r.status === 'assigned').length }; } /** * Get total requested quantity for a specific asset type */ export function getTotalRequestedQuantity( requests: AssetRequest[], assetType: string, statusFilter?: RequestStatus[] ): number { let filteredRequests = requests; if (statusFilter && statusFilter.length > 0) { filteredRequests = requests.filter(req => statusFilter.includes(req.status)); } return filteredRequests.reduce((total, req) => { const assetItem = req.requestedAssets.find(asset => asset.assetType === assetType); return total + (assetItem?.quantity || 0); }, 0); } /** * Get unique asset types from requests */ export function getUniqueAssetTypes(requests: AssetRequest[]): string[] { const assetTypes = new Set(); requests.forEach(req => { req.requestedAssets.forEach(asset => { assetTypes.add(asset.assetType); }); }); return Array.from(assetTypes).sort(); } /** * Calculate average processing time for completed requests (approved or rejected) * @param requests - Array of asset requests * @returns Average processing time in days, or null if no completed requests */ export function calculateAverageProcessingTime(requests: AssetRequest[]): number | null { const completedRequests = requests.filter( req => (req.status === 'approved' || req.status === 'rejected') && req.reviewedDate ); if (completedRequests.length === 0) return null; const totalDays = completedRequests.reduce((sum, req) => { const submitted = parseISO(req.submittedDate); const reviewed = parseISO(req.reviewedDate!); return sum + differenceInDays(reviewed, submitted); }, 0); return Math.round(totalDays / completedRequests.length); } // ============================================================================ // VALIDATION HELPERS // ============================================================================ /** * Check if a request is stale (pending for too long) * @param request - Asset request * @param daysThreshold - Number of days to consider stale (default: 30) * @returns True if request is stale */ export function isStaleRequest(request: AssetRequest, daysThreshold: number = 30): boolean { if (request.status !== 'pending') return false; return calculateRequestAge(request.submittedDate) >= daysThreshold; } /** * Check if a request requires urgent attention (pending for more than 7 days) * @param request - Asset request * @returns True if request requires urgent attention */ export function requiresUrgentAttention(request: AssetRequest): boolean { if (request.status !== 'pending') return false; return calculateRequestAge(request.submittedDate) >= 7; } /** * Get priority level for a request based on age * @param request - Asset request * @returns Priority level: 'low', 'medium', 'high', or 'urgent' */ export function getRequestPriority( request: AssetRequest ): 'low' | 'medium' | 'high' | 'urgent' { if (request.status !== 'pending') return 'low'; const age = calculateRequestAge(request.submittedDate); if (age >= 14) return 'urgent'; if (age >= 7) return 'high'; if (age >= 3) return 'medium'; return 'low'; }