Season view: filter the editor to a year (#54) (#66)
Build image / build-and-push (push) Successful in 17s

Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #66.
This commit is contained in:
2026-07-21 05:47:34 +00:00
committed by steve
parent b96ca1ec0a
commit 8c5ddb21ec
17 changed files with 556 additions and 36 deletions
+34 -6
View File
@@ -12,12 +12,21 @@ import { Palette } from '@/editor/Palette'
import { SeedTray } from '@/editor/SeedTray'
import { ClearBedModal } from '@/editor/ClearBedModal'
import { EditorHint } from '@/editor/EditorHint'
import { SeasonBanner, SeasonPicker } from '@/editor/SeasonPicker'
import { objectDisplayName } from '@/editor/kinds'
import { useEditorStore } from '@/editor/store'
import type { EditorGarden } from '@/editor/types'
import { ShareGardenModal } from '@/components/gardens/ShareGardenModal'
import { useMe } from '@/lib/auth'
import { toEditorObject, useEnsurePlantInFull, useGardenFull, useUpdateObject, useUpdatePlanting } from '@/lib/objects'
import {
toEditorObject,
useEnsurePlantInFull,
useGardenFull,
useGardenSeason,
useGardenYears,
useUpdateObject,
useUpdatePlanting,
} from '@/lib/objects'
import { toEditorPlanting } from '@/lib/plantings'
import type { Plant } from '@/lib/plants'
import { useSeedTray } from '@/lib/seedTray'
@@ -30,7 +39,14 @@ export function GardenEditorPage() {
const gid = Number(gardenId)
const { focus } = routeApi.useSearch()
const navigate = routeApi.useNavigate()
const full = useGardenFull(gid)
const seasonYear = useEditorStore((s) => s.seasonYear)
const setSeasonYear = useEditorStore((s) => s.setSeasonYear)
// Two queries, one shown. The live one stays mounted so returning to "now" is
// instant and so the optimistic mutation cache it owns is never displaced.
const live = useGardenFull(gid)
const season = useGardenSeason(gid, seasonYear)
const full = seasonYear === null ? live : season
const years = useGardenYears(gid)
const me = useMe()
usePageTitle(full.data?.garden.name ?? 'Garden')
@@ -68,7 +84,11 @@ export function GardenEditorPage() {
// Role gating, computed before the effects/returns so the nudge handler can use
// it. Ownership is the authoritative ownerId==me check.
const gd = full.data?.garden
const canEdit = gd != null && (gd.ownerId === me.data?.id || gd.myRole === 'editor')
// A past season is read-only: it is a record of what happened, and editing it
// would be editing the past. This is the single gate — the palette, inspector,
// nudging, placement and drag handles all key off canEdit.
const canEdit =
seasonYear === null && gd != null && (gd.ownerId === me.data?.id || gd.myRole === 'editor')
const isOwner = gd != null && me.data != null && gd.ownerId === me.data.id
// Latest values for the mount-once nudge keydown handler to read, so its effect
@@ -330,9 +350,14 @@ export function GardenEditorPage() {
Share
</Button>
)}
{!canEdit && (
{!canEdit && seasonYear === null && (
<p className="mb-2 rounded-md bg-border/40 px-2 py-1 text-xs text-muted">👁 View only</p>
)}
{years.data && years.data.length > 0 && (
<div className="mb-2">
<SeasonPicker years={years.data} value={seasonYear} onChange={setSeasonYear} />
</div>
)}
{focusedObjectId == null && canEdit && <Palette />}
<Button
variant="ghost"
@@ -343,7 +368,8 @@ export function GardenEditorPage() {
</Button>
</div>
<div className="relative min-h-0 flex-1">
<div className="relative flex min-h-0 flex-1 flex-col gap-2">
{seasonYear !== null && <SeasonBanner year={seasonYear} onExit={() => setSeasonYear(null)} />}
{focusedObject && (
<div className="absolute left-2 top-2 z-20 flex flex-wrap items-center gap-2 rounded-lg border border-border bg-surface/90 px-2 py-1.5 text-sm shadow-sm backdrop-blur">
<button type="button" onClick={exitFocus} className="rounded px-1.5 py-0.5 font-medium text-accent-strong hover:underline">
@@ -380,7 +406,9 @@ export function GardenEditorPage() {
))}
</div>
)}
<GardenCanvas garden={garden} objects={objects} plantings={plantings} plantsById={plantsById} canEdit={canEdit} />
<div className="relative min-h-0 flex-1">
<GardenCanvas garden={garden} objects={objects} plantings={plantings} plantsById={plantsById} canEdit={canEdit} />
</div>
{/* Empty-state hints (non-interactive overlays). */}
{canEdit && focusedObjectId == null && objects.length === 0 && (