Spaces:
Sleeping
Sleeping
| 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<RequestStatus, RequestStatus[]> = { | |
| 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<RequestStatus, number> = { | |
| 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<RequestStatus, number> { | |
| 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<string>(); | |
| 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'; | |
| } | |