Build inventory frontend and stock management features
- 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
This commit is contained in:
88
packages/admin/src/components/inventory/supplier-form.tsx
Normal file
88
packages/admin/src/components/inventory/supplier-form.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user