232 lines
8.7 KiB
JavaScript
232 lines
8.7 KiB
JavaScript
import { useState } from 'react'
|
|
import BreedSelect from './BreedSelect'
|
|
|
|
const STATUS_OPTIONS = [
|
|
{ value: 'active', label: 'Active' },
|
|
{ value: 'for_sale', label: 'For Sale' },
|
|
{ value: 'on_hold', label: 'On Hold' },
|
|
{ value: 'sold', label: 'Sold' },
|
|
{ value: 'deceased', label: 'Deceased' },
|
|
{ value: 'other', label: 'Other' },
|
|
]
|
|
|
|
const SEX_OPTIONS = [
|
|
{ value: 'buck', label: 'Buck' },
|
|
{ value: 'doe', label: 'Doe' },
|
|
{ value: 'unknown', label: 'Unknown' },
|
|
]
|
|
|
|
function lbsToOz(decimalLbs) {
|
|
if (decimalLbs === null || decimalLbs === undefined || decimalLbs === '') return { lbs: '', oz: '' }
|
|
const total = Number(decimalLbs)
|
|
if (Number.isNaN(total)) return { lbs: '', oz: '' }
|
|
const lbs = Math.floor(total)
|
|
const oz = Math.round((total - lbs) * 16)
|
|
return { lbs: String(lbs), oz: String(oz) }
|
|
}
|
|
|
|
function ozToLbs(lbs, oz) {
|
|
const l = Number(lbs) || 0
|
|
const o = Number(oz) || 0
|
|
return l + o / 16
|
|
}
|
|
|
|
function RabbitDetailsForm({ value, onChange, onPhotoChange, weightUnit = 'decimal_lbs', showValidation = false }) {
|
|
const [photoPreview, setPhotoPreview] = useState(null)
|
|
const { lbs: ozLbs, oz: ozOz } = lbsToOz(value.weight_lbs)
|
|
|
|
function setField(field, fieldValue) {
|
|
onChange({ [field]: fieldValue })
|
|
}
|
|
|
|
function handlePhotoSelect(e) {
|
|
const file = e.target.files?.[0]
|
|
if (!file) return
|
|
onPhotoChange?.(file)
|
|
setPhotoPreview(URL.createObjectURL(file))
|
|
}
|
|
|
|
const nameInvalid = showValidation && !value.name?.trim()
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="md:col-span-2">
|
|
<label className="block font-body text-sm font-medium text-text-primary">
|
|
Name <span className="text-danger">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={value.name ?? ''}
|
|
onChange={(e) => setField('name', e.target.value)}
|
|
className={`mt-1 w-full rounded-md border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent ${
|
|
nameInvalid ? 'border-danger' : 'border-border'
|
|
}`}
|
|
/>
|
|
{nameInvalid && <p className="mt-1 font-body text-xs text-danger">Name is required</p>}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Farm / Prefix</label>
|
|
<input
|
|
type="text"
|
|
value={value.farm ?? ''}
|
|
onChange={(e) => setField('farm', e.target.value)}
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Sex</label>
|
|
<div className="mt-1 flex gap-1">
|
|
{SEX_OPTIONS.map((opt) => (
|
|
<button
|
|
key={opt.value}
|
|
type="button"
|
|
onClick={() => setField('sex', opt.value)}
|
|
className={`flex-1 rounded-md border px-3 py-2 font-body text-sm ${
|
|
value.sex === opt.value
|
|
? 'border-accent bg-accent text-surface'
|
|
: 'border-border bg-surface-raised text-text-primary hover:bg-surface-sunken'
|
|
}`}
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Date of Birth</label>
|
|
<input
|
|
type="date"
|
|
value={value.dob ?? ''}
|
|
onChange={(e) => setField('dob', e.target.value)}
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Acquired Date</label>
|
|
<input
|
|
type="date"
|
|
value={value.acquired_date ?? ''}
|
|
onChange={(e) => setField('acquired_date', e.target.value || null)}
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
<p className="mt-1 font-body text-xs text-text-secondary">If added to the herd after birth</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Death Date</label>
|
|
<input
|
|
type="date"
|
|
value={value.death_date ?? ''}
|
|
onChange={(e) => setField('death_date', e.target.value || null)}
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Weight</label>
|
|
{weightUnit === 'lbs_oz' ? (
|
|
<div className="mt-1 flex gap-2">
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
value={ozLbs}
|
|
onChange={(e) => setField('weight_lbs', ozToLbs(e.target.value, ozOz))}
|
|
placeholder="lbs"
|
|
className="w-1/2 rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
max="15"
|
|
value={ozOz}
|
|
onChange={(e) => setField('weight_lbs', ozToLbs(ozLbs, e.target.value))}
|
|
placeholder="oz"
|
|
className="w-1/2 rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
step="0.01"
|
|
value={value.weight_lbs ?? ''}
|
|
onChange={(e) => setField('weight_lbs', e.target.value === '' ? null : Number(e.target.value))}
|
|
placeholder="decimal lbs"
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Ear Number</label>
|
|
<input
|
|
type="text"
|
|
value={value.ear_number ?? ''}
|
|
onChange={(e) => setField('ear_number', e.target.value.toUpperCase())}
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm uppercase focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Location</label>
|
|
<input
|
|
type="text"
|
|
value={value.location ?? ''}
|
|
onChange={(e) => setField('location', e.target.value)}
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Status</label>
|
|
<select
|
|
value={value.status ?? 'active'}
|
|
onChange={(e) => setField('status', e.target.value)}
|
|
className="mt-1 w-full rounded-md border border-border bg-surface-raised px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
>
|
|
{STATUS_OPTIONS.map((opt) => (
|
|
<option key={opt.value} value={opt.value}>
|
|
{opt.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Breed</label>
|
|
<BreedSelect value={value.breed ?? ''} onChange={(breed) => setField('breed', breed)} />
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block font-body text-sm font-medium text-text-primary">Color Name</label>
|
|
<input
|
|
type="text"
|
|
value={value.color_name ?? ''}
|
|
onChange={(e) => setField('color_name', e.target.value)}
|
|
placeholder="e.g. Broken Black Tort"
|
|
className="mt-1 w-full rounded-md border border-border px-3 py-2 font-body text-sm focus:outline-none focus:ring-2 focus:ring-accent"
|
|
/>
|
|
</div>
|
|
|
|
<div className="md:col-span-2">
|
|
<label className="block font-body text-sm font-medium text-text-primary">Photo</label>
|
|
<input
|
|
type="file"
|
|
accept="image/jpeg,image/png,image/webp"
|
|
onChange={handlePhotoSelect}
|
|
className="mt-1 block w-full font-body text-sm text-text-secondary"
|
|
/>
|
|
{photoPreview && (
|
|
<img src={photoPreview} alt="Preview" className="mt-2 h-24 w-24 rounded-md object-cover" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default RabbitDetailsForm
|