Add gardens UI: list page + create/edit/delete (#8)
The /gardens page is now home base, backed by the #7 API. - lib/units.ts: cm <-> meters/feet conversion + formatCm/formatDimensions (metric "m", imperial "ft/in"). Minimal for #8; #9's geometry lib consolidates. - lib/gardens.ts: zod gardenSchema + useGardens/useCreateGarden/ useUpdateGarden/useDeleteGarden (mutations invalidate the list) and conflictGarden() to pull the fresh row out of a 409. - GardensPage: loading/empty/error states; empty state prompts a first garden; responsive card grid (single column on phones). - GardenCard: name, dimensions formatted per the garden's unit, notes preview; body links into /gardens/:id, edit/delete kept out of the link. - GardenFormModal: create/edit with a unit selector; dimensions are entered in the chosen unit and converted to cm (switching units converts the current values). A 409 rebases the form onto the server's fresh row. - DeleteGardenModal: confirmation before removing. - ui/: Modal (Escape/backdrop close), TextArea, Select. Verified in a real browser against the embedded binary: create appears without reload; edit persists (version 2), form prefilled from stored cm converted back to the garden's unit; delete confirms then removes -> empty state; an imperial 4 ft x 8 ft garden stores 122 x 244 cm and shows "4' 0" x 8' 0"". tsc --noEmit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// Unit conversion + display for garden dimensions. The API and DB are metric
|
||||
// only (centimeters); imperial is purely a display/entry concern here. A minimal
|
||||
// helper set for #8 — #9's geometry lib consolidates/extends it.
|
||||
|
||||
export type UnitPref = 'metric' | 'imperial'
|
||||
|
||||
const CM_PER_INCH = 2.54
|
||||
const CM_PER_METER = 100
|
||||
const INCHES_PER_FOOT = 12
|
||||
|
||||
/** Feet (+ optional inches) → whole centimeters. */
|
||||
export function cmFromFtIn(feet: number, inches = 0): number {
|
||||
return Math.round((feet * INCHES_PER_FOOT + inches) * CM_PER_INCH)
|
||||
}
|
||||
|
||||
/** Meters → whole centimeters. */
|
||||
export function cmFromMeters(meters: number): number {
|
||||
return Math.round(meters * CM_PER_METER)
|
||||
}
|
||||
|
||||
/** A value typed in the given unit (meters, or feet) → centimeters. */
|
||||
export function cmFromDisplay(value: number, unit: UnitPref): number {
|
||||
return unit === 'imperial' ? cmFromFtIn(value) : cmFromMeters(value)
|
||||
}
|
||||
|
||||
/** Centimeters → a number in the given unit, for prefilling an input field. */
|
||||
export function displayFromCm(cm: number, unit: UnitPref): number {
|
||||
const value = unit === 'imperial' ? cm / CM_PER_INCH / INCHES_PER_FOOT : cm / CM_PER_METER
|
||||
// Trim floating-point noise (e.g. 122cm → 4.0026ft → 4).
|
||||
return Math.round(value * 100) / 100
|
||||
}
|
||||
|
||||
/** Centimeters → a human string in the given unit (e.g. "1.22 m" or "4′ 0″"). */
|
||||
export function formatCm(cm: number, unit: UnitPref): string {
|
||||
if (unit === 'imperial') {
|
||||
const totalInches = cm / CM_PER_INCH
|
||||
let feet = Math.floor(totalInches / INCHES_PER_FOOT)
|
||||
let inches = Math.round(totalInches - feet * INCHES_PER_FOOT)
|
||||
if (inches === INCHES_PER_FOOT) {
|
||||
feet += 1
|
||||
inches = 0
|
||||
}
|
||||
return `${feet}′ ${inches}″`
|
||||
}
|
||||
const meters = Math.round((cm / CM_PER_METER) * 100) / 100
|
||||
return `${meters} m`
|
||||
}
|
||||
|
||||
/** Centimeters width × height → a human string in the given unit. */
|
||||
export function formatDimensions(widthCm: number, heightCm: number, unit: UnitPref): string {
|
||||
return `${formatCm(widthCm, unit)} × ${formatCm(heightCm, unit)}`
|
||||
}
|
||||
|
||||
/** The input-field label for a single dimension in the given unit. */
|
||||
export function dimensionUnitLabel(unit: UnitPref): string {
|
||||
return unit === 'imperial' ? 'ft' : 'm'
|
||||
}
|
||||
Reference in New Issue
Block a user