- Full inventory UI: product list with search/filter, product detail with tabs (details, units, suppliers, stock receipts, price history) - Product filters: category, type (serialized/rental/repair), low stock, active/inactive — all server-side with URL-synced state - Product-supplier junction: link products to multiple suppliers with preferred flag, joined supplier details in UI - Stock receipts: record incoming stock with supplier, qty, cost per unit, invoice number; auto-increments qty_on_hand for non-serialized products - Price history tab on product detail page - categories/all endpoint to avoid pagination limit on dropdown fetches - categoryId filter on product list endpoint - Repair parts and additional inventory items in music store seed data - isDualUseRepair corrected: instruments set to false, strings/parts true - Product-supplier links and stock receipts in seed data - Price history seed data simulating cost increases over past year - 37 API tests covering categories, suppliers, products, units, product-suppliers, and stock receipts - alert-dialog and checkbox UI components - sync-and-deploy.sh script for rsync + remote deploy
89 lines
3.3 KiB
TypeScript
89 lines
3.3 KiB
TypeScript
import { useForm } from 'react-hook-form'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import type { Supplier } from '@/types/inventory'
|
|
|
|
interface Props {
|
|
defaultValues?: Partial<Supplier>
|
|
onSubmit: (data: Record<string, unknown>) => void
|
|
onDelete?: () => void
|
|
loading?: boolean
|
|
deleteLoading?: boolean
|
|
}
|
|
|
|
export function SupplierForm({ defaultValues, onSubmit, onDelete, loading, deleteLoading }: Props) {
|
|
const { register, handleSubmit } = useForm({
|
|
defaultValues: {
|
|
name: defaultValues?.name ?? '',
|
|
contactName: defaultValues?.contactName ?? '',
|
|
email: defaultValues?.email ?? '',
|
|
phone: defaultValues?.phone ?? '',
|
|
website: defaultValues?.website ?? '',
|
|
accountNumber: defaultValues?.accountNumber ?? '',
|
|
paymentTerms: defaultValues?.paymentTerms ?? '',
|
|
},
|
|
})
|
|
|
|
function handleFormSubmit(data: Record<string, string>) {
|
|
onSubmit({
|
|
name: data.name,
|
|
contactName: data.contactName || undefined,
|
|
email: data.email || undefined,
|
|
phone: data.phone || undefined,
|
|
website: data.website || undefined,
|
|
accountNumber: data.accountNumber || undefined,
|
|
paymentTerms: data.paymentTerms || undefined,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit(handleFormSubmit)} className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sup-name">Name *</Label>
|
|
<Input id="sup-name" {...register('name')} placeholder="e.g. Fender Musical Instruments" required />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sup-contact">Contact Name</Label>
|
|
<Input id="sup-contact" {...register('contactName')} placeholder="Jane Smith" />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sup-email">Email</Label>
|
|
<Input id="sup-email" type="email" {...register('email')} placeholder="orders@supplier.com" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sup-phone">Phone</Label>
|
|
<Input id="sup-phone" {...register('phone')} placeholder="555-0100" />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sup-website">Website</Label>
|
|
<Input id="sup-website" {...register('website')} placeholder="https://supplier.com" />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sup-acct">Account Number</Label>
|
|
<Input id="sup-acct" {...register('accountNumber')} placeholder="ACC-12345" />
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sup-terms">Payment Terms</Label>
|
|
<Input id="sup-terms" {...register('paymentTerms')} placeholder="Net 30" />
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-2 pt-2">
|
|
<Button type="submit" disabled={loading} className="flex-1">
|
|
{loading ? 'Saving...' : defaultValues?.id ? 'Save Changes' : 'Create Supplier'}
|
|
</Button>
|
|
{onDelete && (
|
|
<Button type="button" variant="destructive" disabled={deleteLoading} onClick={onDelete}>
|
|
{deleteLoading ? 'Deleting...' : 'Delete'}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</form>
|
|
)
|
|
}
|