devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
10.4 kB
import React 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,
} from '@/components/ui/form';
import { CustomDropdown } from './shared/CustomDropdown';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Loader2, Save, X } from 'lucide-react';
import {
Asset,
AssetCreateRequest,
AssetUpdateRequest,
AssetCreateSchema,
AssetUpdateSchema,
ASSET_TYPES,
ASSET_CONDITIONS
} from '@/types/asset-management';
interface AssetFormProps {
asset?: Asset;
onSubmit: (data: AssetCreateRequest | AssetUpdateRequest) => Promise<void>;
onCancel: () => void;
isSubmitting?: boolean;
title?: string;
}
type AssetFormValues = {
assetCode: string;
assetType: string;
brandModel: string;
serialNumber: string;
assetCondition: string;
};
const AssetForm: React.FC<AssetFormProps> = ({
asset,
onSubmit,
onCancel,
isSubmitting = false,
title
}) => {
const isEditing = !!asset;
const formTitle = title || (isEditing ? 'Edit Asset' : 'Create New Asset');
const form = useForm<AssetFormValues>({
resolver: zodResolver(isEditing ? AssetUpdateSchema.omit({ assetID: true }) : AssetCreateSchema),
defaultValues: {
assetCode: asset?.assetCode || '',
assetType: asset?.assetType || '',
brandModel: asset?.brandModel || '',
serialNumber: asset?.serialNumber || '',
assetCondition: asset?.assetCondition || '',
},
});
const handleSubmit = async (values: AssetFormValues) => {
try {
if (isEditing && asset) {
const updateData: AssetUpdateRequest = {
assetID: asset.assetID,
...values,
};
await onSubmit(updateData);
} else {
const createData: AssetCreateRequest = 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('Serial number')) {
form.setError('serialNumber', {
type: 'manual',
message: 'This serial number is already in use. Please enter a unique serial number.'
});
} else if (error.message.includes('Brand model')) {
form.setError('brandModel', {
type: 'manual',
message: 'Invalid brand model format.'
});
} else if (error.message.includes('Asset type')) {
form.setError('assetType', {
type: 'manual',
message: 'Please select a valid asset type.'
});
} else {
// Set a general form error
form.setError('root', {
type: 'manual',
message: error.message
});
}
}
}
};
return (
<Card className="w-full max-w-2xl mx-auto">
<CardHeader className="pb-4">
<CardTitle className="flex items-center gap-2 text-lg sm:text-xl">
{formTitle}
</CardTitle>
</CardHeader>
<CardContent className="px-4 sm:px-6">
<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"
role="alert"
aria-live="polite"
>
<p className="text-sm text-destructive font-medium">
{form.formState.errors.root.message}
</p>
</div>
)}
{/* Asset Code */}
<FormField
control={form.control}
name="assetCode"
render={({ field }) => (
<FormItem>
<FormLabel>Asset Code *</FormLabel>
<FormControl>
<Input
placeholder="e.g., LAP001, DES002, MON003"
className="font-mono"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Asset Type */}
<FormField
control={form.control}
name="assetType"
render={({ field }) => (
<FormItem>
<FormLabel>Asset Type *</FormLabel>
<FormControl>
<CustomDropdown
options={ASSET_TYPES.map(type => ({ value: type, label: type }))}
value={field.value}
onChange={field.onChange}
placeholder="Select asset type"
error={!!form.formState.errors.assetType}
aria-label="Asset type selection"
aria-describedby={form.formState.errors.assetType ? 'assetType-error' : undefined}
/>
</FormControl>
<FormMessage id="assetType-error" />
</FormItem>
)}
/>
{/* Brand/Model */}
<FormField
control={form.control}
name="brandModel"
render={({ field }) => (
<FormItem>
<FormLabel>Brand/Model *</FormLabel>
<FormControl>
<Input
placeholder="e.g., Dell Latitude 7420, HP EliteBook 840"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Serial Number */}
<FormField
control={form.control}
name="serialNumber"
render={({ field }) => (
<FormItem>
<FormLabel>Serial Number *</FormLabel>
<FormControl>
<Input
placeholder="Enter unique serial number"
className="font-mono"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Asset Condition */}
<FormField
control={form.control}
name="assetCondition"
render={({ field }) => (
<FormItem>
<FormLabel>Asset Condition *</FormLabel>
<FormControl>
<CustomDropdown
options={ASSET_CONDITIONS.map(condition => ({ value: condition, label: condition }))}
value={field.value}
onChange={field.onChange}
placeholder="Select asset condition"
error={!!form.formState.errors.assetCondition}
aria-label="Asset condition selection"
aria-describedby={form.formState.errors.assetCondition ? 'assetCondition-error' : undefined}
/>
</FormControl>
<FormMessage id="assetCondition-error" />
</FormItem>
)}
/>
{/* Form Actions */}
<div className="flex flex-col-reverse sm:flex-row justify-end gap-3 pt-4">
<Button
type="button"
variant="outline"
onClick={onCancel}
disabled={isSubmitting}
className="flex items-center justify-center gap-2 w-full sm:w-auto min-h-10"
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onCancel();
}
}}
tabIndex={0}
role="button"
aria-label="Cancel form and return to previous page"
>
<X className="h-4 w-4" aria-hidden="true" />
Cancel
</Button>
<Button
type="submit"
disabled={isSubmitting}
className="flex items-center justify-center gap-2 w-full sm:w-auto min-h-10"
aria-describedby={isSubmitting ? 'submit-status' : undefined}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
if (!isSubmitting) {
// Trigger form submission
const form = e.currentTarget.closest('form');
if (form) {
form.requestSubmit();
}
}
}
}}
tabIndex={0}
role="button"
aria-label={isEditing ? 'Update asset information' : 'Create new asset'}
>
{isSubmitting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
<span id="submit-status" className="sr-only">
{isEditing ? 'Updating asset...' : 'Creating asset...'}
</span>
</>
) : (
<Save className="h-4 w-4" aria-hidden="true" />
)}
{isSubmitting
? (isEditing ? 'Updating...' : 'Creating...')
: (isEditing ? 'Update Asset' : 'Create Asset')
}
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
};
export default AssetForm;