pmtool / src /components /asset-management /SystemDetailsList.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
15 kB
import React, { useState, useMemo } from 'react';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { CustomDropdown } from './shared/CustomDropdown';
import { Badge } from '@/components/ui/badge';
import {
Search,
Eye,
Edit,
Trash2,
Plus,
ArrowUpDown,
ArrowUp,
ArrowDown,
Monitor,
Laptop,
HardDrive
} from 'lucide-react';
import { SystemDetails, SystemDetailsWithAsset } from '@/types/asset-management';
import { LoadingSpinner } from './shared';
import Pagination from './shared/Pagination';
import { usePagination } from '@/hooks/asset-management/usePagination';
interface SystemDetailsListProps {
systemDetails: SystemDetails[] | SystemDetailsWithAsset[];
isLoading?: boolean;
onView?: (systemDetails: SystemDetails) => void;
onEdit?: (systemDetails: SystemDetails) => void;
onDelete?: (systemDetails: SystemDetails) => void;
onCreate?: () => void;
onNavigateToAsset?: (assetId: number) => void;
showActions?: boolean;
showAssetInfo?: boolean;
}
type SortField = keyof SystemDetails;
type SortDirection = 'asc' | 'desc';
interface SortConfig {
field: SortField;
direction: SortDirection;
}
const SystemDetailsList: React.FC<SystemDetailsListProps> = ({
systemDetails,
isLoading = false,
onView,
onEdit,
onDelete,
onCreate,
onNavigateToAsset,
showActions = true,
showAssetInfo = true
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [manufacturerFilter, setManufacturerFilter] = useState<string>('all');
const [sortConfig, setSortConfig] = useState<SortConfig>({ field: 'systemDetailsID', direction: 'asc' });
// Helper function to get system type icon
const getSystemTypeIcon = (systemModel: string) => {
const model = systemModel.toLowerCase();
if (model.includes('laptop') || model.includes('notebook')) return <Laptop className="h-4 w-4" />;
if (model.includes('monitor') || model.includes('display')) return <Monitor className="h-4 w-4" />;
return <HardDrive className="h-4 w-4" />;
};
// Get unique manufacturers for filter
const uniqueManufacturers = useMemo(() => {
const manufacturers = systemDetails.map(details => details.manufacturer);
return Array.from(new Set(manufacturers)).sort();
}, [systemDetails]);
// Filter and sort system details
const filteredAndSortedSystemDetails = useMemo(() => {
let filtered = systemDetails.filter(details => {
const matchesSearch =
details.systemModel.toLowerCase().includes(searchTerm.toLowerCase()) ||
details.manufacturer.toLowerCase().includes(searchTerm.toLowerCase()) ||
details.processor.toLowerCase().includes(searchTerm.toLowerCase()) ||
details.installedRAM.toLowerCase().includes(searchTerm.toLowerCase()) ||
details.graphics.toLowerCase().includes(searchTerm.toLowerCase()) ||
details.diskModel.toLowerCase().includes(searchTerm.toLowerCase()) ||
details.assetID.toString().includes(searchTerm);
const matchesManufacturer = manufacturerFilter === 'all' || details.manufacturer === manufacturerFilter;
return matchesSearch && matchesManufacturer;
});
// Sort system details
filtered.sort((a, b) => {
const aValue = a[sortConfig.field];
const bValue = b[sortConfig.field];
if (typeof aValue === 'string' && typeof bValue === 'string') {
const comparison = aValue.localeCompare(bValue);
return sortConfig.direction === 'asc' ? comparison : -comparison;
}
if (typeof aValue === 'number' && typeof bValue === 'number') {
const comparison = aValue - bValue;
return sortConfig.direction === 'asc' ? comparison : -comparison;
}
return 0;
});
return filtered;
}, [systemDetails, searchTerm, manufacturerFilter, sortConfig]);
// Use pagination hook
const {
currentPage,
itemsPerPage,
totalPages,
paginatedData: paginatedSystemDetails,
setCurrentPage,
setItemsPerPage,
} = usePagination(filteredAndSortedSystemDetails);
const handleSort = (field: SortField) => {
setSortConfig(prev => ({
field,
direction: prev.field === field && prev.direction === 'asc' ? 'desc' : 'asc'
}));
};
const getSortIcon = (field: SortField) => {
if (sortConfig.field !== field) {
return <ArrowUpDown className="h-4 w-4" />;
}
return sortConfig.direction === 'asc'
? <ArrowUp className="h-4 w-4" />
: <ArrowDown className="h-4 w-4" />;
};
if (isLoading) {
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold">System Details</h2>
</div>
<LoadingSpinner size="lg" text="Loading system details..." className="py-8" />
</div>
);
}
return (
<div className="space-y-4">
{/* Header */}
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold">System Details</h2>
{onCreate && (
<Button onClick={onCreate} className="flex items-center gap-2">
<Plus className="h-4 w-4" />
Add System Details
</Button>
)}
</div>
{/* Filters */}
<div className="flex flex-col sm:flex-row gap-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input
placeholder="Search by model, manufacturer, processor, RAM, graphics, or disk..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<CustomDropdown
options={[
{ value: 'all', label: 'All Manufacturers' },
...uniqueManufacturers.map(manufacturer => ({ value: manufacturer, label: manufacturer }))
]}
value={manufacturerFilter}
onChange={setManufacturerFilter}
placeholder="Filter by manufacturer"
className="w-full sm:w-48"
aria-label="Filter by manufacturer"
/>
</div>
{/* Results count */}
<div className="text-sm text-muted-foreground">
Showing {paginatedSystemDetails.length} of {filteredAndSortedSystemDetails.length} system details
{filteredAndSortedSystemDetails.length !== systemDetails.length && (
<span> (filtered from {systemDetails.length} total)</span>
)}
</div>
{/* Table */}
<div className="border rounded-lg">
<Table>
<TableHeader>
<TableRow>
<TableHead
className="cursor-pointer hover:bg-muted/50"
onClick={() => handleSort('systemDetailsID')}
>
<div className="flex items-center gap-2">
ID
{getSortIcon('systemDetailsID')}
</div>
</TableHead>
{showAssetInfo && (
<TableHead
className="cursor-pointer hover:bg-muted/50"
onClick={() => handleSort('assetID')}
>
<div className="flex items-center gap-2">
Asset ID
{getSortIcon('assetID')}
</div>
</TableHead>
)}
<TableHead
className="cursor-pointer hover:bg-muted/50"
onClick={() => handleSort('systemModel')}
>
<div className="flex items-center gap-2">
System Model
{getSortIcon('systemModel')}
</div>
</TableHead>
<TableHead
className="cursor-pointer hover:bg-muted/50"
onClick={() => handleSort('manufacturer')}
>
<div className="flex items-center gap-2">
Manufacturer
{getSortIcon('manufacturer')}
</div>
</TableHead>
<TableHead
className="cursor-pointer hover:bg-muted/50"
onClick={() => handleSort('processor')}
>
<div className="flex items-center gap-2">
Processor
{getSortIcon('processor')}
</div>
</TableHead>
<TableHead
className="cursor-pointer hover:bg-muted/50"
onClick={() => handleSort('installedRAM')}
>
<div className="flex items-center gap-2">
RAM
{getSortIcon('installedRAM')}
</div>
</TableHead>
<TableHead>Storage</TableHead>
{showActions && <TableHead className="text-right">Actions</TableHead>}
</TableRow>
</TableHeader>
<TableBody>
{paginatedSystemDetails.length === 0 ? (
<TableRow>
<TableCell
colSpan={showActions ? (showAssetInfo ? 8 : 7) : (showAssetInfo ? 7 : 6)}
className="text-center py-8 text-muted-foreground"
>
{systemDetails.length === 0
? "No system details found. Create your first system details record to get started."
: "No system details match your current filters."
}
</TableCell>
</TableRow>
) : (
paginatedSystemDetails.map((details) => {
const detailsWithAsset = details as SystemDetailsWithAsset;
return (
<TableRow key={details.systemDetailsID} className="hover:bg-muted/50">
<TableCell className="font-medium">{details.systemDetailsID}</TableCell>
{showAssetInfo && (
<TableCell>
<div className="flex items-center gap-2">
<Badge variant="outline">{details.assetID}</Badge>
{detailsWithAsset.asset && (
<span className="text-sm text-muted-foreground">
{detailsWithAsset.asset.assetType}
</span>
)}
{onNavigateToAsset && (
<Button
variant="ghost"
size="sm"
className="h-6 px-2 text-xs"
onClick={() => onNavigateToAsset(details.assetID)}
>
View Asset
</Button>
)}
</div>
</TableCell>
)}
<TableCell>
<div className="flex items-center gap-2">
{getSystemTypeIcon(details.systemModel)}
<span>{details.systemModel}</span>
</div>
</TableCell>
<TableCell>{details.manufacturer}</TableCell>
<TableCell className="max-w-48 truncate" title={details.processor}>
{details.processor}
</TableCell>
<TableCell>
<Badge variant="secondary">{details.installedRAM}</Badge>
</TableCell>
<TableCell>
<div className="text-sm">
<div className="font-medium">{details.diskSize}</div>
<div className="text-muted-foreground text-xs truncate max-w-32" title={details.diskModel}>
{details.diskModel}
</div>
</div>
</TableCell>
{showActions && (
<TableCell className="text-right">
<div className="flex justify-end gap-2">
{onView && (
<Button
variant="ghost"
size="sm"
onClick={() => onView(details)}
className="h-8 w-8 p-0"
>
<Eye className="h-4 w-4" />
<span className="sr-only">View system details</span>
</Button>
)}
{onEdit && (
<Button
variant="ghost"
size="sm"
onClick={() => onEdit(details)}
className="h-8 w-8 p-0"
>
<Edit className="h-4 w-4" />
<span className="sr-only">Edit system details</span>
</Button>
)}
{onDelete && (
<Button
variant="ghost"
size="sm"
onClick={() => onDelete(details)}
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
<span className="sr-only">Delete system details</span>
</Button>
)}
</div>
</TableCell>
)}
</TableRow>
);
})
)}
</TableBody>
</Table>
</div>
{/* Pagination */}
{filteredAndSortedSystemDetails.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
totalItems={filteredAndSortedSystemDetails.length}
itemsPerPage={itemsPerPage}
onPageChange={setCurrentPage}
onItemsPerPageChange={setItemsPerPage}
className="mt-4"
/>
)}
</div>
);
};
export default SystemDetailsList;