pmtool / src /pages /asset-requests.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
10.2 kB
import React, { useState } from 'react';
import { useAuth } from '@/lib/auth-context';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import Layout from '@/components/layout/layout';
import { Card, CardContent } from '@/components/ui/card';
import { Package, Loader2, AlertCircle, Home } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import AssetRequestForm from '@/components/asset-request/AssetRequestForm';
import MyAssetRequests from '@/components/asset-request/MyAssetRequests';
import AcknowledgeDialog from '@/components/asset-request/AcknowledgeDialog';
import ErrorBoundary from '@/components/asset-request/ErrorBoundary';
import { ErrorDisplay } from '@/components/asset-request/ErrorDisplay';
import { AssetRequestFormSkeleton, MyAssetRequestsSkeleton } from '@/components/asset-request/LoadingSkeletons';
import { assetRequestApi } from '@/services/assetRequestApi';
import { AssetRequestCreateRequest } from '@/types/asset-request';
import { notifyRequestSubmitted, notifyError } from '@/utils/assetRequestNotifications';
import { showToast } from '@/components/toast-utils';
const AssetRequestsPage: React.FC = () => {
const { userData } = useAuth();
const queryClient = useQueryClient();
const [isSubmitting, setIsSubmitting] = useState(false);
const [showAcknowledgeDialog, setShowAcknowledgeDialog] = useState(false);
const [selectedRequestId, setSelectedRequestId] = useState<number | null>(null);
// Get employee ID from user data
const employeeID = userData?.employeeId || 0;
// Fetch employee's asset requests
const {
data: requests = [],
isLoading,
error,
refetch,
} = useQuery({
queryKey: ['assetRequests', 'employee', employeeID],
queryFn: () => assetRequestApi.getByEmployeeId(employeeID),
enabled: employeeID > 0,
staleTime: 30000, // 30 seconds
refetchInterval: 60000, // Refresh every minute
});
// Create asset request mutation with optimistic updates
const createRequestMutation = useMutation({
mutationFn: (data: AssetRequestCreateRequest) => assetRequestApi.create(data),
onMutate: async (newRequestData) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: ['assetRequests', 'employee', employeeID] });
// Snapshot the previous value
const previousRequests = queryClient.getQueryData(['assetRequests', 'employee', employeeID]);
// Optimistically update with a temporary request
const optimisticRequest = {
requestID: Date.now(), // Temporary ID
employeeID: newRequestData.employeeID,
employeeName: userData ? `${userData.firstName} ${userData.lastName}` : '',
requestedAssets: newRequestData.requestedAssets,
justification: newRequestData.justification,
status: 'pending' as const,
submittedDate: new Date().toISOString(),
reviewedDate: null,
reviewedBy: null,
reviewerName: null,
adminComments: null,
assignedDate: null,
assignedBy: null,
assignerName: null,
};
queryClient.setQueryData(
['assetRequests', 'employee', employeeID],
(old: any) => [optimisticRequest, ...(old || [])]
);
return { previousRequests };
},
onSuccess: (newRequest) => {
// Invalidate and refetch to get the real data
queryClient.invalidateQueries({ queryKey: ['assetRequests', 'employee', employeeID] });
queryClient.invalidateQueries({ queryKey: ['assetRequests', 'pending'] });
// Show success notification
notifyRequestSubmitted(newRequest);
},
onError: (error: Error, _newRequest, context) => {
// Rollback optimistic update
if (context?.previousRequests) {
queryClient.setQueryData(
['assetRequests', 'employee', employeeID],
context.previousRequests
);
}
// Show error notification
notifyError('Submit Request', error.message || 'Failed to submit asset request. Please try again.');
},
});
// Acknowledge mutation
const acknowledgeMutation = useMutation({
mutationFn: ({ requestId, comments }: { requestId: number; comments: string }) =>
assetRequestApi.acknowledge(requestId, {
acknowledgementComments: comments || undefined,
acknowledgedBy: employeeID,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['assetRequests', 'employee', employeeID] });
showToast('success', 'Asset receipt acknowledged successfully');
},
onError: (error: Error) => {
notifyError('Acknowledge Receipt', error.message || 'Failed to acknowledge asset receipt. Please try again.');
},
});
const handleSubmitRequest = async (data: AssetRequestCreateRequest) => {
setIsSubmitting(true);
try {
await createRequestMutation.mutateAsync(data);
} finally {
setIsSubmitting(false);
}
};
const handleAcknowledge = (requestId: number) => {
setSelectedRequestId(requestId);
setShowAcknowledgeDialog(true);
};
const handleAcknowledgeConfirm = async (comments: string) => {
if (!selectedRequestId) return;
await acknowledgeMutation.mutateAsync({ requestId: selectedRequestId, comments });
setSelectedRequestId(null);
};
const handleRetry = () => {
refetch();
};
// Loading state
if (!userData) {
return (
<Layout>
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin text-primary mx-auto mb-4" />
<p className="text-muted-foreground">Loading user information...</p>
</div>
</div>
</Layout>
);
}
// Error state for missing employee ID
if (employeeID === 0) {
return (
<Layout>
<div className="flex items-center justify-center min-h-[400px]">
<Card className="max-w-md">
<CardContent className="pt-6">
<div className="text-center">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h2 className="text-xl font-semibold mb-2">Employee Profile Not Found</h2>
<p className="text-muted-foreground mb-4">
Your user account is not linked to an employee profile. Please contact your
administrator to set up your employee profile.
</p>
</div>
</CardContent>
</Card>
</div>
</Layout>
);
}
return (
<Layout>
{/* Breadcrumb Navigation */}
<Breadcrumb className="mb-4">
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link to="/dashboard" className="flex items-center gap-1">
<Home className="h-4 w-4" />
<span>Home</span>
</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link to="/assets">Asset Management</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>My Asset Requests</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
{/* Page Header */}
<div className="mb-6">
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900 flex items-center gap-2">
<Package className="h-6 w-6 sm:h-8 sm:w-8 shrink-0" aria-hidden="true" />
<span>Asset Requests</span>
</h1>
<p className="text-muted-foreground mt-1 text-sm sm:text-base">
Request assets you need and track the status of your requests
</p>
</div>
{/* Error State */}
{error && (
<div className="mb-6">
<ErrorDisplay
error={error}
title="Failed to Load Requests"
onRetry={handleRetry}
variant="card"
/>
</div>
)}
{/* Two-Column Layout */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left Column: Request Form */}
<div className="order-2 lg:order-1">
<ErrorBoundary onReset={() => window.location.reload()}>
{isLoading ? (
<AssetRequestFormSkeleton />
) : (
<AssetRequestForm
employeeID={employeeID}
onSubmit={handleSubmitRequest}
isSubmitting={isSubmitting}
/>
)}
</ErrorBoundary>
</div>
{/* Right Column: My Requests */}
<div className="order-1 lg:order-2">
<ErrorBoundary onReset={handleRetry}>
{isLoading ? (
<MyAssetRequestsSkeleton />
) : (
<MyAssetRequests
requests={requests}
isLoading={isLoading}
onAcknowledge={handleAcknowledge}
/>
)}
</ErrorBoundary>
</div>
</div>
{/* Acknowledge Dialog */}
{selectedRequestId && (
<AcknowledgeDialog
isOpen={showAcknowledgeDialog}
onClose={() => {
setShowAcknowledgeDialog(false);
setSelectedRequestId(null);
}}
onConfirm={handleAcknowledgeConfirm}
requestId={selectedRequestId}
employeeName={userData?.firstName + ' ' + userData?.lastName || ''}
/>
)}
</Layout>
);
};
export default AssetRequestsPage;