Spaces:
Sleeping
Sleeping
| import React from 'react'; | |
| import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { Badge } from '@/components/ui/badge'; | |
| import { Button } from '@/components/ui/button'; | |
| import { | |
| Monitor, | |
| Laptop, | |
| Smartphone, | |
| Printer, | |
| HardDrive, | |
| Eye, | |
| Edit, | |
| Trash2, | |
| User, | |
| Calendar, | |
| Mouse, | |
| MousePointer2, | |
| Keyboard, | |
| Tablet, | |
| Scan, | |
| Computer, | |
| UserPlus | |
| } from 'lucide-react'; | |
| import { Asset, AssetWithDetails, AssetCondition } from '@/types/asset-management'; | |
| import { format } from 'date-fns'; | |
| // Helper function to get badge variant based on asset condition | |
| const getConditionBadgeVariant = (condition: AssetCondition) => { | |
| switch (condition) { | |
| case 'Excellent': | |
| return 'default'; | |
| case 'Good': | |
| return 'secondary'; | |
| case 'Fair': | |
| return 'outline'; | |
| case 'Poor': | |
| return 'destructive'; | |
| default: | |
| return 'outline'; | |
| } | |
| }; | |
| // Helper function to get asset type icon | |
| const getAssetTypeIcon = (assetType: string) => { | |
| const type = assetType.toLowerCase(); | |
| // Exact matches first | |
| if (type === 'laptop') return <Laptop className="h-4 w-4" />; | |
| if (type === 'desktop') return <Computer className="h-4 w-4" />; | |
| if (type === 'monitor') return <Monitor className="h-4 w-4" />; | |
| if (type === 'extended screen') return <Monitor className="h-4 w-4" />; | |
| if (type === 'keyboard') return <Keyboard className="h-4 w-4" />; | |
| if (type === 'mouse') return <Mouse className="h-4 w-4" />; | |
| if (type === 'wired mouse') return <Mouse className="h-4 w-4" />; | |
| if (type === 'wireless mouse') return <MousePointer2 className="h-4 w-4" />; | |
| if (type === 'laptop stand') return <Laptop className="h-4 w-4" />; | |
| if (type === 'printer') return <Printer className="h-4 w-4" />; | |
| if (type === 'scanner') return <Scan className="h-4 w-4" />; | |
| if (type === 'tablet') return <Tablet className="h-4 w-4" />; | |
| if (type === 'phone') return <Smartphone className="h-4 w-4" />; | |
| // Fallback for partial matches | |
| if (type.includes('laptop')) return <Laptop className="h-4 w-4" />; | |
| if (type.includes('monitor') || type.includes('screen')) return <Monitor className="h-4 w-4" />; | |
| if (type.includes('mouse')) return <Mouse className="h-4 w-4" />; | |
| if (type.includes('keyboard')) return <Keyboard className="h-4 w-4" />; | |
| if (type.includes('phone')) return <Smartphone className="h-4 w-4" />; | |
| if (type.includes('tablet')) return <Tablet className="h-4 w-4" />; | |
| if (type.includes('printer')) return <Printer className="h-4 w-4" />; | |
| if (type.includes('scanner')) return <Scan className="h-4 w-4" />; | |
| return <HardDrive className="h-4 w-4" />; | |
| }; | |
| interface AssetCardProps { | |
| asset: Asset | AssetWithDetails; | |
| onView?: (asset: Asset) => void; | |
| onEdit?: (asset: Asset) => void; | |
| onDelete?: (asset: Asset) => void; | |
| onAssign?: (asset: Asset) => void; | |
| activeAssignment?: any; | |
| showActions?: boolean; | |
| compact?: boolean; | |
| } | |
| const AssetCard: React.FC<AssetCardProps> = ({ | |
| asset, | |
| onView, | |
| onEdit, | |
| onDelete, | |
| onAssign, | |
| activeAssignment, | |
| showActions = true, | |
| compact = false | |
| }) => { | |
| const assetWithDetails = asset as AssetWithDetails; | |
| const currentAssignment = activeAssignment || | |
| assetWithDetails.currentAssignment || | |
| (assetWithDetails.assignments && assetWithDetails.assignments.find(a => !a.returnedDate)); | |
| const isAvailable = !currentAssignment; | |
| return ( | |
| <Card className="w-full hover:shadow-md transition-shadow p-0"> | |
| <CardHeader className={compact ? 'pb-3 pt-6 px-6' : 'pb-4 pt-8 px-8'}> | |
| {/* Assignment Status Badge - Mobile First */} | |
| <div className="mb-3"> | |
| {currentAssignment ? ( | |
| <Badge variant="secondary" className="flex items-center gap-1.5 w-fit text-xs md:text-xs py-1.5 px-2.5"> | |
| <User className="h-3.5 w-3.5 md:h-3 md:w-3" /> | |
| <span>Assigned to {currentAssignment.employeeName}</span> | |
| </Badge> | |
| ) : ( | |
| <Badge variant="outline" className="flex items-center gap-1.5 w-fit text-green-600 border-green-600 text-xs md:text-xs py-1.5 px-2.5"> | |
| <span>Available</span> | |
| </Badge> | |
| )} | |
| </div> | |
| <div className="flex justify-between items-start gap-4"> | |
| <CardTitle className={`font-semibold flex items-center gap-2 ${compact ? 'text-base' : 'text-lg'}`}> | |
| {getAssetTypeIcon(asset.assetType)} | |
| <span>{asset.assetType}</span> | |
| </CardTitle> | |
| <Badge variant={getConditionBadgeVariant(asset.assetCondition as AssetCondition)} className="text-xs py-1 px-2"> | |
| {asset.assetCondition} | |
| </Badge> | |
| </div> | |
| </CardHeader> | |
| <CardContent className={compact ? 'pb-4 pt-0 px-6' : 'pb-6 pt-0 px-8'}> | |
| <div className={compact ? 'space-y-3 md:space-y-4' : 'space-y-5'}> | |
| <div className={compact ? 'text-sm md:text-sm' : 'text-base'}> | |
| <span className="font-medium">Asset Code:</span> | |
| <span className="ml-2 text-muted-foreground font-mono break-all">{asset.assetCode}</span> | |
| </div> | |
| <div className={compact ? 'text-sm md:text-sm' : 'text-base'}> | |
| <span className="font-medium">Brand/Model:</span> | |
| <span className="ml-2 text-muted-foreground break-words">{asset.brandModel}</span> | |
| </div> | |
| <div className={compact ? 'text-sm md:text-sm' : 'text-base'}> | |
| <span className="font-medium">Serial Number:</span> | |
| <span className="ml-2 text-muted-foreground font-mono break-all">{asset.serialNumber}</span> | |
| </div> | |
| {/* Assignment Status Details */} | |
| {currentAssignment ? ( | |
| <div className={`flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-0 text-sm text-green-600 bg-green-50 dark:bg-green-950 rounded-md ${compact ? 'p-3' : 'p-4'}`}> | |
| <div className="flex items-center"> | |
| <User className="h-4 w-4 mr-2 shrink-0" /> | |
| <span className="font-medium">Assigned</span> | |
| </div> | |
| {currentAssignment.assignedDate && ( | |
| <span className="text-xs text-muted-foreground sm:ml-2"> | |
| since {format(new Date(currentAssignment.assignedDate), 'PP')} | |
| </span> | |
| )} | |
| </div> | |
| ) : ( | |
| <div className={`flex items-center text-sm text-muted-foreground bg-muted rounded-md ${compact ? 'p-3' : 'p-4'}`}> | |
| <Calendar className="h-4 w-4 mr-2 shrink-0" /> | |
| <span>Available</span> | |
| </div> | |
| )} | |
| {/* System Details Preview */} | |
| {assetWithDetails.systemDetails && !compact && ( | |
| <div className="text-xs text-muted-foreground bg-muted/50 p-4 rounded-md"> | |
| <div className="font-medium mb-3">System Details:</div> | |
| <div className="space-y-2"> | |
| <div className="break-words">{assetWithDetails.systemDetails.systemModel}</div> | |
| <div className="break-words">{assetWithDetails.systemDetails.processor}</div> | |
| <div>{assetWithDetails.systemDetails.installedRAM} RAM</div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| {showActions && ( | |
| <CardFooter className={compact ? 'pt-4 pb-6 px-6' : 'pt-5 pb-8 px-8'}> | |
| <div className="flex flex-wrap justify-end w-full gap-2"> | |
| {onAssign && isAvailable && ( | |
| <Button | |
| variant="default" | |
| size="sm" | |
| onClick={() => onAssign(asset)} | |
| className="flex items-center gap-1 min-h-[44px] md:min-h-0 touch-manipulation" | |
| aria-label={`Assign asset ${asset.assetCode} to employee`} | |
| > | |
| <UserPlus className="h-4 w-4 md:h-3 md:w-3" /> | |
| <span className="text-sm md:text-xs">Assign</span> | |
| </Button> | |
| )} | |
| {onView && ( | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={() => onView(asset)} | |
| className="flex items-center gap-1 min-h-[44px] md:min-h-0 touch-manipulation" | |
| aria-label={`View asset ${asset.assetCode}`} | |
| > | |
| <Eye className="h-4 w-4 md:h-3 md:w-3" /> | |
| <span className="text-sm md:text-xs">View</span> | |
| </Button> | |
| )} | |
| {onEdit && ( | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={() => onEdit(asset)} | |
| className="flex items-center gap-1 min-h-[44px] md:min-h-0 touch-manipulation" | |
| aria-label={`Edit asset ${asset.assetCode}`} | |
| > | |
| <Edit className="h-4 w-4 md:h-3 md:w-3" /> | |
| <span className="text-sm md:text-xs">Edit</span> | |
| </Button> | |
| )} | |
| {onDelete && ( | |
| <Button | |
| variant="destructive" | |
| size="sm" | |
| onClick={() => onDelete(asset)} | |
| className="flex items-center gap-1 min-h-[44px] md:min-h-0 touch-manipulation" | |
| aria-label={`Delete asset ${asset.assetCode}`} | |
| > | |
| <Trash2 className="h-4 w-4 md:h-3 md:w-3" /> | |
| <span className="text-sm md:text-xs">Delete</span> | |
| </Button> | |
| )} | |
| </div> | |
| </CardFooter> | |
| )} | |
| </Card> | |
| ); | |
| }; | |
| export default AssetCard; |