feat(GRO-1173): apply buffer rules UI changes to extracted groombook/web repo
This commit ports the GRO-1173 admin UI changes from the app monorepo into the extracted groombook/web repo, using the correct source paths (src/ instead of apps/web/src/): - New BufferRulesSection component (full CRUD UI for /api/buffer-rules) - Default Buffer (minutes) field added to service create/edit form - Size Category and Coat Type dropdowns added to PetForm (portal) - @groombook/types Service interface extended with defaultBufferMinutes - BufferRulesSection embedded in Settings page The PetForm already had coatType — this commit adds petSizeCategory and renders both fields with proper dropdown selectors. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -4,6 +4,8 @@ import type { Pet, MedicalAlert, CoatType, AlertSeverity } from "@groombook/type
|
||||
|
||||
const COAT_TYPES: CoatType[] = ["double", "wire", "curly", "smooth", "long", "hairless"];
|
||||
const SEVERITY_OPTIONS: AlertSeverity[] = ["low", "medium", "high"];
|
||||
const SIZE_OPTIONS = ["small", "medium", "large", "xlarge"] as const;
|
||||
type SizeOption = typeof SIZE_OPTIONS[number];
|
||||
|
||||
interface Props {
|
||||
pet?: Pet;
|
||||
@@ -21,6 +23,7 @@ export function PetForm({ pet, onSave, onCancel }: Props) {
|
||||
const [weight, setWeight] = useState(pet?.weightKg ?? 0);
|
||||
const [notes, setNotes] = useState(pet?.healthAlerts ?? "");
|
||||
const [coatType, setCoatType] = useState<CoatType | "">((pet?.coatType as CoatType) ?? "");
|
||||
const [petSizeCategory, setPetSizeCategory] = useState<SizeOption | "">(pet?.petSizeCategory as SizeOption ?? "");
|
||||
const [preferredCuts, setPreferredCuts] = useState<string[]>(pet?.preferredCuts ?? []);
|
||||
const [cutInput, setCutInput] = useState("");
|
||||
const [alerts, setAlerts] = useState<Omit<MedicalAlert, "id">[]>(
|
||||
@@ -81,6 +84,7 @@ export function PetForm({ pet, onSave, onCancel }: Props) {
|
||||
weightKg: weight || null,
|
||||
healthAlerts: notes,
|
||||
coatType: coatType || null,
|
||||
petSizeCategory: petSizeCategory || null,
|
||||
preferredCuts,
|
||||
medicalAlerts: alerts.map((a, i) => ({ ...a, id: pet.medicalAlerts?.[i]?.id ?? crypto.randomUUID() })),
|
||||
};
|
||||
@@ -159,6 +163,22 @@ export function PetForm({ pet, onSave, onCancel }: Props) {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Size Category */}
|
||||
<div>
|
||||
<label htmlFor="size-category" className="block text-sm font-medium text-stone-600 mb-1">Size Category</label>
|
||||
<select
|
||||
id="size-category"
|
||||
value={petSizeCategory}
|
||||
onChange={e => setPetSizeCategory(e.target.value as SizeOption)}
|
||||
className="w-full border border-stone-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-(--color-accent) bg-white"
|
||||
>
|
||||
<option value="">Select size</option>
|
||||
{SIZE_OPTIONS.map(s => (
|
||||
<option key={s} value={s}>{s.charAt(0).toUpperCase() + s.slice(1)}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Temperament (read-only) */}
|
||||
{(temperamentScore != null || temperamentFlags.length > 0) && (
|
||||
<div className="bg-stone-50 rounded-xl p-4 space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user