pmtool / src /components /asset-management /SystemDetailsForm.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
16.5 kB
import React, { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
FormDescription,
} from '@/components/ui/form';
import { CustomDropdown } from './shared/CustomDropdown';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Loader2, Save, X, Monitor, Search } from 'lucide-react';
import {
SystemDetails,
SystemDetailsCreateRequest,
SystemDetailsUpdateRequest,
SystemDetailsCreateSchema,
SystemDetailsUpdateSchema,
Asset
} from '@/types/asset-management';
import { useAssets } from '@/hooks/asset-management/useAssets';
interface SystemDetailsFormProps {
systemDetails?: SystemDetails;
preselectedAssetId?: number;
onSubmit: (data: SystemDetailsCreateRequest | SystemDetailsUpdateRequest) => Promise<void>;
onCancel: () => void;
isSubmitting?: boolean;
title?: string;
}
type SystemDetailsFormValues = {
assetID: number;
systemModel: string;
processor: string;
installedRAM: string;
graphics: string;
screenResolution: string;
diskModel: string;
diskSize: string;
manufacturer: string;
};
const SystemDetailsForm: React.FC<SystemDetailsFormProps> = ({
systemDetails,
preselectedAssetId,
onSubmit,
onCancel,
isSubmitting = false,
title
}) => {
const isEditing = !!systemDetails;
const formTitle = title || (isEditing ? 'Edit System Details' : 'Create System Details');
const { data: assets = [], isLoading: isLoadingAssets } = useAssets();
const [assetSearch, setAssetSearch] = useState('');
const form = useForm<SystemDetailsFormValues>({
resolver: zodResolver(isEditing ? SystemDetailsUpdateSchema.omit({ systemDetailsID: true }) : SystemDetailsCreateSchema),
defaultValues: {
assetID: systemDetails?.assetID || preselectedAssetId || 0,
systemModel: systemDetails?.systemModel || '',
processor: systemDetails?.processor || '',
installedRAM: systemDetails?.installedRAM || '',
graphics: systemDetails?.graphics || '',
screenResolution: systemDetails?.screenResolution || '',
diskModel: systemDetails?.diskModel || '',
diskSize: systemDetails?.diskSize || '',
manufacturer: systemDetails?.manufacturer || '',
},
});
// Filter assets based on search term
const filteredAssets = assets.filter(asset =>
asset.assetType.toLowerCase().includes(assetSearch.toLowerCase()) ||
asset.brandModel.toLowerCase().includes(assetSearch.toLowerCase()) ||
asset.serialNumber.toLowerCase().includes(assetSearch.toLowerCase()) ||
asset.assetCode.toLowerCase().includes(assetSearch)
);
const handleSubmit = async (values: SystemDetailsFormValues) => {
try {
if (isEditing && systemDetails) {
const updateData: SystemDetailsUpdateRequest = {
systemDetailsID: systemDetails.systemDetailsID,
...values,
};
await onSubmit(updateData);
} else {
const createData: SystemDetailsCreateRequest = values;
await onSubmit(createData);
}
} catch (error) {
console.error('Form submission error:', error);
// Set form errors if validation failed
if (error instanceof Error) {
if (error.message.includes('Asset with ID') && error.message.includes('does not exist')) {
form.setError('assetID', {
type: 'manual',
message: 'The selected asset does not exist. Please select a valid asset.'
});
} else if (error.message.includes('System details already exist')) {
form.setError('assetID', {
type: 'manual',
message: 'System details already exist for this asset. Please select a different asset.'
});
} else if (error.message.includes('System model')) {
form.setError('systemModel', {
type: 'manual',
message: 'Invalid system model format.'
});
} else if (error.message.includes('Processor')) {
form.setError('processor', {
type: 'manual',
message: 'Invalid processor information.'
});
} else {
// Set a general form error
form.setError('root', {
type: 'manual',
message: error.message
});
}
}
}
};
// Common RAM options
const ramOptions = [
'4GB', '8GB', '16GB', '32GB', '64GB', '128GB'
];
// Common screen resolutions
const resolutionOptions = [
'1366x768', '1920x1080', '2560x1440', '3840x2160', '2560x1600', '1440x900'
];
// Common disk sizes
const diskSizeOptions = [
'128GB', '256GB', '512GB', '1TB', '2TB', '4TB'
];
return (
<Card className="w-full max-w-4xl mx-auto">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Monitor className="h-5 w-5" />
{formTitle}
</CardTitle>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6">
{/* Display form-level errors */}
{form.formState.errors.root && (
<div className="bg-destructive/15 border border-destructive/20 rounded-md p-3">
<p className="text-sm text-destructive font-medium">
{form.formState.errors.root.message}
</p>
</div>
)}
{/* Asset Selection */}
<FormField
control={form.control}
name="assetID"
render={({ field }) => (
<FormItem>
<FormLabel>Asset *</FormLabel>
<FormDescription>
Select the asset for which you want to add system details.
</FormDescription>
<div className="space-y-2">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input
placeholder="Search assets..."
value={assetSearch}
onChange={(e) => setAssetSearch(e.target.value)}
className="pl-10"
/>
</div>
<FormControl>
<CustomDropdown
options={filteredAssets.map(asset => ({
value: asset.assetID.toString(),
label: `${asset.assetCode} - ${asset.assetType} - ${asset.brandModel}`
}))}
value={field.value?.toString() || ''}
onChange={(value) => field.onChange(parseInt(value))}
placeholder="Select an asset"
disabled={isLoadingAssets || (isEditing && !!systemDetails)}
error={!!form.formState.errors.assetID}
aria-label="Asset selection"
aria-describedby={form.formState.errors.assetID ? 'assetID-error' : undefined}
/>
</FormControl>
</div>
<FormMessage id="assetID-error" />
</FormItem>
)}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* System Model */}
<FormField
control={form.control}
name="systemModel"
render={({ field }) => (
<FormItem>
<FormLabel>System Model *</FormLabel>
<FormControl>
<Input
placeholder="e.g., Dell Latitude 7420, MacBook Pro 16"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Manufacturer */}
<FormField
control={form.control}
name="manufacturer"
render={({ field }) => (
<FormItem>
<FormLabel>Manufacturer *</FormLabel>
<FormControl>
<Input
placeholder="e.g., Dell, HP, Apple, Lenovo"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Processor */}
<FormField
control={form.control}
name="processor"
render={({ field }) => (
<FormItem>
<FormLabel>Processor *</FormLabel>
<FormControl>
<Input
placeholder="e.g., Intel Core i7-11800H, AMD Ryzen 7 5800H"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Installed RAM */}
<FormField
control={form.control}
name="installedRAM"
render={({ field }) => (
<FormItem>
<FormLabel>Installed RAM *</FormLabel>
<FormControl>
<CustomDropdown
options={[
...ramOptions.map(ram => ({ value: ram, label: ram })),
{ value: 'custom', label: 'Custom Amount' }
]}
value={field.value}
onChange={field.onChange}
placeholder="Select RAM amount"
error={!!form.formState.errors.installedRAM}
aria-label="RAM amount selection"
aria-describedby={form.formState.errors.installedRAM ? 'installedRAM-error' : undefined}
/>
</FormControl>
{field.value === 'custom' && (
<Input
placeholder="Enter custom RAM amount"
onChange={(e) => field.onChange(e.target.value)}
className="mt-2"
/>
)}
<FormMessage id="installedRAM-error" />
</FormItem>
)}
/>
{/* Graphics */}
<FormField
control={form.control}
name="graphics"
render={({ field }) => (
<FormItem>
<FormLabel>Graphics *</FormLabel>
<FormControl>
<Input
placeholder="e.g., NVIDIA RTX 3060, Intel Iris Xe"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Screen Resolution */}
<FormField
control={form.control}
name="screenResolution"
render={({ field }) => (
<FormItem>
<FormLabel>Screen Resolution *</FormLabel>
<FormControl>
<CustomDropdown
options={[
...resolutionOptions.map(resolution => ({ value: resolution, label: resolution })),
{ value: 'custom', label: 'Custom Resolution' }
]}
value={field.value}
onChange={field.onChange}
placeholder="Select resolution"
error={!!form.formState.errors.screenResolution}
aria-label="Screen resolution selection"
aria-describedby={form.formState.errors.screenResolution ? 'screenResolution-error' : undefined}
/>
</FormControl>
{field.value === 'custom' && (
<Input
placeholder="Enter custom resolution (e.g., 2880x1800)"
onChange={(e) => field.onChange(e.target.value)}
className="mt-2"
/>
)}
<FormMessage id="screenResolution-error" />
</FormItem>
)}
/>
{/* Disk Model */}
<FormField
control={form.control}
name="diskModel"
render={({ field }) => (
<FormItem>
<FormLabel>Disk Model *</FormLabel>
<FormControl>
<Input
placeholder="e.g., Samsung SSD 980 PRO, WD Blue SN570"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Disk Size */}
<FormField
control={form.control}
name="diskSize"
render={({ field }) => (
<FormItem>
<FormLabel>Disk Size *</FormLabel>
<FormControl>
<CustomDropdown
options={[
...diskSizeOptions.map(size => ({ value: size, label: size })),
{ value: 'custom', label: 'Custom Size' }
]}
value={field.value}
onChange={field.onChange}
placeholder="Select disk size"
error={!!form.formState.errors.diskSize}
aria-label="Disk size selection"
aria-describedby={form.formState.errors.diskSize ? 'diskSize-error' : undefined}
/>
</FormControl>
{field.value === 'custom' && (
<Input
placeholder="Enter custom disk size"
onChange={(e) => field.onChange(e.target.value)}
className="mt-2"
/>
)}
<FormMessage id="diskSize-error" />
</FormItem>
)}
/>
</div>
{/* Form Actions */}
<div className="flex justify-end gap-3 pt-4">
<Button
type="button"
variant="outline"
onClick={onCancel}
disabled={isSubmitting}
className="flex items-center gap-2"
>
<X className="h-4 w-4" />
Cancel
</Button>
<Button
type="submit"
disabled={isSubmitting}
className="flex items-center gap-2"
>
{isSubmitting ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Save className="h-4 w-4" />
)}
{isSubmitting
? (isEditing ? 'Updating...' : 'Creating...')
: (isEditing ? 'Update System Details' : 'Create System Details')
}
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
};
export default SystemDetailsForm;