pmtool / src /utils /assetRequestHelpers.ts
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
12.6 kB
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';
}