Some checks failed
Build & Deploy Frontend / build-push-deploy (push) Failing after 15s
335 lines
12 KiB
TypeScript
335 lines
12 KiB
TypeScript
import { useState } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { z } from 'zod';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from '@/components/ui/form';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { Asset, useAssets } from '@/hooks/useAssets';
|
|
|
|
const assetSchema = z.object({
|
|
asset_code: z.string().min(1, 'Asset code is required'),
|
|
name: z.string().min(1, 'Asset name is required'),
|
|
description: z.string().optional(),
|
|
asset_category_id: z.string().optional(),
|
|
condition: z.enum(['new', 'used', 'refurbished', 'damaged']),
|
|
status: z.enum(['in_use', 'available', 'maintenance', 'disposed', 'lost']),
|
|
assigned_to_department: z.string().optional(),
|
|
location_id: z.string().optional(),
|
|
purchase_cost: z.number().min(0, 'Purchase cost must be positive'),
|
|
current_book_value: z.number().min(0, 'Book value must be positive'),
|
|
model_number: z.string().optional(),
|
|
serial_number: z.string().optional(),
|
|
sub_category: z.string().optional(),
|
|
});
|
|
|
|
type AssetFormData = z.infer<typeof assetSchema>;
|
|
|
|
interface AssetFormProps {
|
|
asset?: Asset;
|
|
onSuccess?: () => void;
|
|
onCancel?: () => void;
|
|
}
|
|
|
|
export function AssetForm({ asset, onSuccess, onCancel }: AssetFormProps) {
|
|
const { createAsset, updateAsset, categories, locations, isCreating, isUpdating } = useAssets();
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
const form = useForm<AssetFormData>({
|
|
resolver: zodResolver(assetSchema),
|
|
defaultValues: {
|
|
asset_code: asset?.asset_code || '',
|
|
name: asset?.name || '',
|
|
description: asset?.description || '',
|
|
asset_category_id: asset?.asset_category_id || '',
|
|
condition: asset?.condition || 'new',
|
|
status: asset?.status || 'available',
|
|
assigned_to_department: asset?.assigned_to_department || '',
|
|
location_id: asset?.location_id || '',
|
|
purchase_cost: asset?.purchase_cost || 0,
|
|
current_book_value: asset?.current_book_value || 0,
|
|
model_number: asset?.model_number || '',
|
|
serial_number: asset?.serial_number || '',
|
|
sub_category: asset?.sub_category || '',
|
|
},
|
|
});
|
|
|
|
const onSubmit = async (data: AssetFormData) => {
|
|
setIsSubmitting(true);
|
|
try {
|
|
if (asset) {
|
|
updateAsset({ id: asset.id, ...data } as Partial<Asset> & { id: string });
|
|
} else {
|
|
createAsset(data as Omit<Asset, 'id' | 'user_id' | 'created_at' | 'updated_at'>);
|
|
}
|
|
onSuccess?.();
|
|
} catch (error) {
|
|
console.error('Error saving asset:', error);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className="w-full max-w-2xl mx-auto">
|
|
<CardHeader>
|
|
<CardTitle>{asset ? 'Edit Asset' : 'Create New Asset'}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="asset_code"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Asset Code *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="e.g., LAP-001" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Asset Name *</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="e.g., Dell Laptop" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="description"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Description</FormLabel>
|
|
<FormControl>
|
|
<Textarea
|
|
placeholder="Detailed description of the asset..."
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="asset_category_id"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Category</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select category" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
{categories?.map((category) => (
|
|
<SelectItem key={category.id} value={category.id}>
|
|
{category.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="location_id"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Location</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select location" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
{locations?.map((location) => (
|
|
<SelectItem key={location.id} value={location.id}>
|
|
{location.location_name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="condition"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Condition</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select condition" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="new">New</SelectItem>
|
|
<SelectItem value="used">Used</SelectItem>
|
|
<SelectItem value="refurbished">Refurbished</SelectItem>
|
|
<SelectItem value="damaged">Damaged</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="status"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Status</FormLabel>
|
|
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
<FormControl>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select status" />
|
|
</SelectTrigger>
|
|
</FormControl>
|
|
<SelectContent>
|
|
<SelectItem value="available">Available</SelectItem>
|
|
<SelectItem value="in_use">In Use</SelectItem>
|
|
<SelectItem value="maintenance">Maintenance</SelectItem>
|
|
<SelectItem value="disposed">Disposed</SelectItem>
|
|
<SelectItem value="lost">Lost</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="purchase_cost"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Purchase Cost</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
step="0.01"
|
|
{...field}
|
|
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="current_book_value"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Current Book Value</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
step="0.01"
|
|
{...field}
|
|
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="model_number"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Model Number</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="e.g., Latitude 5520" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={form.control}
|
|
name="serial_number"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Serial Number</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="e.g., DL5520123456" {...field} />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex justify-end space-x-2">
|
|
{onCancel && (
|
|
<Button type="button" variant="outline" onClick={onCancel}>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
<Button
|
|
type="submit"
|
|
disabled={isSubmitting || isCreating || isUpdating}
|
|
>
|
|
{isSubmitting || isCreating || isUpdating ? 'Saving...' : 'Save Asset'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |