MyKindlebox/mykindlebox/client/src/components/RabbitDetailsForm.jsx

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