Spaces:
Sleeping
Sleeping
| 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 { Textarea } from '@/components/ui/textarea'; | |
| 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, Package } from 'lucide-react'; | |
| import { | |
| AssetItem, | |
| AssetItemCreateRequest, | |
| AssetItemUpdateRequest, | |
| AssetItemSchema, | |
| ASSET_ITEM_TYPES, | |
| ASSET_CONDITIONS, | |
| } from '@/types/asset-management'; | |
| interface AssetItemFormProps { | |
| item?: AssetItem; | |
| assetId?: number; | |
| onSubmit: (data: AssetItemCreateRequest | AssetItemUpdateRequest) => Promise<void>; | |
| onCancel: () => void; | |
| isSubmitting?: boolean; | |
| } | |
| type AssetItemFormValues = { | |
| itemType: string; | |
| itemName: string; | |
| quantity: number; | |
| specifications: string; | |
| serialNumber?: string; | |
| condition: string; | |
| notes?: string; | |
| }; | |
| const AssetItemForm: React.FC<AssetItemFormProps> = ({ | |
| item, | |
| assetId, | |
| onSubmit, | |
| onCancel, | |
| isSubmitting = false, | |
| }) => { | |
| const isEditing = !!item; | |
| const form = useForm<AssetItemFormValues>({ | |
| resolver: zodResolver(AssetItemSchema.omit({ itemID: true, assetID: true, addedDate: true })), | |
| defaultValues: { | |
| itemType: item?.itemType || '', | |
| itemName: item?.itemName || '', | |
| quantity: item?.quantity || 1, | |
| specifications: item?.specifications || '', | |
| serialNumber: item?.serialNumber || '', | |
| condition: item?.condition || 'Good', | |
| notes: item?.notes || '', | |
| }, | |
| }); | |
| const handleSubmit = async (values: AssetItemFormValues) => { | |
| try { | |
| const addedDate = new Date().toISOString(); | |
| if (isEditing && item) { | |
| const updateData: AssetItemUpdateRequest = { | |
| itemID: item.itemID, | |
| assetID: item.assetID, | |
| ...values, | |
| addedDate: item.addedDate, | |
| }; | |
| await onSubmit(updateData); | |
| } else if (assetId) { | |
| const createData: AssetItemCreateRequest = { | |
| assetID: assetId, | |
| ...values, | |
| addedDate, | |
| }; | |
| await onSubmit(createData); | |
| } | |
| } catch (error) { | |
| console.error('Form submission error:', error); | |
| } | |
| }; | |
| return ( | |
| <Card className="w-full mx-auto"> | |
| <CardHeader> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Package className="h-5 w-5" /> | |
| {isEditing ? 'Edit Item' : 'Add New Item'} | |
| </CardTitle> | |
| </CardHeader> | |
| <CardContent> | |
| <Form {...form}> | |
| <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6"> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| {/* Item Type */} | |
| <FormField | |
| control={form.control} | |
| name="itemType" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Item Type *</FormLabel> | |
| <FormControl> | |
| <CustomDropdown | |
| options={ASSET_ITEM_TYPES.map(type => ({ | |
| value: type, | |
| label: type | |
| }))} | |
| value={field.value} | |
| onChange={field.onChange} | |
| placeholder="Select item type" | |
| error={!!form.formState.errors.itemType} | |
| /> | |
| </FormControl> | |
| <FormDescription> | |
| Select the type of component or item. | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| {/* Item Name */} | |
| <FormField | |
| control={form.control} | |
| name="itemName" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Item Name *</FormLabel> | |
| <FormControl> | |
| <Input | |
| placeholder="e.g., Kingston 8GB DDR4" | |
| {...field} | |
| /> | |
| </FormControl> | |
| <FormDescription> | |
| Enter the specific name or model of the item. | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| {/* Quantity */} | |
| <FormField | |
| control={form.control} | |
| name="quantity" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Quantity *</FormLabel> | |
| <FormControl> | |
| <Input | |
| type="number" | |
| min="1" | |
| placeholder="1" | |
| {...field} | |
| onChange={(e) => field.onChange(parseInt(e.target.value) || 1)} | |
| /> | |
| </FormControl> | |
| <FormDescription> | |
| Number of units of this item. | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| {/* Condition */} | |
| <FormField | |
| control={form.control} | |
| name="condition" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Condition *</FormLabel> | |
| <FormControl> | |
| <CustomDropdown | |
| options={ASSET_CONDITIONS.map(condition => ({ | |
| value: condition, | |
| label: condition | |
| }))} | |
| value={field.value} | |
| onChange={field.onChange} | |
| placeholder="Select condition" | |
| error={!!form.formState.errors.condition} | |
| /> | |
| </FormControl> | |
| <FormDescription> | |
| Current condition of the item. | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| {/* Specifications */} | |
| <FormField | |
| control={form.control} | |
| name="specifications" | |
| render={({ field }) => ( | |
| <FormItem className="md:col-span-2"> | |
| <FormLabel>Specifications *</FormLabel> | |
| <FormControl> | |
| <Input | |
| placeholder="e.g., 8GB DDR4 2666MHz" | |
| {...field} | |
| /> | |
| </FormControl> | |
| <FormDescription> | |
| Technical specifications or details about the item. | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| {/* Serial Number */} | |
| <FormField | |
| control={form.control} | |
| name="serialNumber" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Serial Number</FormLabel> | |
| <FormControl> | |
| <Input | |
| placeholder="Optional" | |
| {...field} | |
| /> | |
| </FormControl> | |
| <FormDescription> | |
| Serial number if available. | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| </div> | |
| {/* Notes */} | |
| <FormField | |
| control={form.control} | |
| name="notes" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Notes</FormLabel> | |
| <FormControl> | |
| <Textarea | |
| placeholder="Additional notes about this item..." | |
| className="min-h-20" | |
| {...field} | |
| /> | |
| </FormControl> | |
| <FormDescription> | |
| Any additional information about this item. | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| {/* Form Actions */} | |
| <div className="flex flex-col 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 min-h-[44px] md:min-h-0 w-full sm:w-auto" | |
| > | |
| <X className="h-4 w-4" /> | |
| <span>Cancel</span> | |
| </Button> | |
| <Button | |
| type="submit" | |
| disabled={isSubmitting} | |
| className="flex items-center justify-center gap-2 min-h-[44px] md:min-h-0 w-full sm:w-auto" | |
| > | |
| {isSubmitting ? ( | |
| <Loader2 className="h-4 w-4 animate-spin" /> | |
| ) : ( | |
| <Save className="h-4 w-4" /> | |
| )} | |
| <span> | |
| {isSubmitting | |
| ? (isEditing ? 'Updating...' : 'Adding...') | |
| : (isEditing ? 'Update Item' : 'Add Item') | |
| } | |
| </span> | |
| </Button> | |
| </div> | |
| </form> | |
| </Form> | |
| </CardContent> | |
| </Card> | |
| ); | |
| }; | |
| export default AssetItemForm; | |