Seed shelf UI: source links, lots, and what's left (#51) (#68)
Build image / build-and-push (push) Successful in 11s
Build image / build-and-push (push) Successful in 11s
Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #68.
This commit is contained in:
@@ -31,6 +31,7 @@ import {
|
||||
import { toEditorPlanting } from '@/lib/plantings'
|
||||
import type { Plant } from '@/lib/plants'
|
||||
import { useJournalCounts } from '@/lib/journal'
|
||||
import { attributableLots, lotsByPlant, useSeedLots, type SeedLot } from '@/lib/seedLots'
|
||||
import { useSeedTray } from '@/lib/seedTray'
|
||||
import { usePageTitle } from '@/lib/usePageTitle'
|
||||
|
||||
@@ -74,6 +75,8 @@ export function GardenEditorPage() {
|
||||
const updateObject = useUpdateObject(gid)
|
||||
const ensurePlant = useEnsurePlantInFull(gid)
|
||||
const { trayPlants, add: addToTray, remove: removeFromTray } = useSeedTray(gid)
|
||||
const seedLots = useSeedLots()
|
||||
const lotsByPlantId = useMemo(() => lotsByPlant(seedLots.data), [seedLots.data])
|
||||
|
||||
// Which plant-picker flow is open: 'place' arms a plant for repeat placement;
|
||||
// 'change' swaps the selected plop's plant.
|
||||
@@ -277,9 +280,18 @@ export function GardenEditorPage() {
|
||||
// Arm a plant for tap-to-place. The full catalog carries plants not yet in this
|
||||
// garden's /full payload, so make sure the chosen one is in the cache first —
|
||||
// otherwise its placed plops render without an icon/color until a refetch.
|
||||
function armPlant(plant: Plant) {
|
||||
// The Seed Tray arms a plant directly, without going through the picker, so it
|
||||
// has to do the picker's single-lot auto-attribution itself — otherwise the
|
||||
// quickest way to place a plant is the one path that silently loses which
|
||||
// packet it came from. Several lots is genuinely ambiguous, so that case stays
|
||||
// unattributed rather than guessing; the picker is where you choose.
|
||||
function armPlant(plant: Plant, lot?: SeedLot) {
|
||||
ensurePlant(plant)
|
||||
setArmedPlant(plant)
|
||||
if (lot === undefined) {
|
||||
const lots = attributableLots(lotsByPlantId.get(plant.id) ?? [])
|
||||
lot = lots.length === 1 ? lots[0] : undefined
|
||||
}
|
||||
setArmedPlant(plant, lot?.id ?? null)
|
||||
}
|
||||
|
||||
// Removing the armed plant from the tray also disarms it, so placement doesn't
|
||||
@@ -294,14 +306,14 @@ export function GardenEditorPage() {
|
||||
// a not-yet-placed plant isn't in that map, which used to silently abort the
|
||||
// pick (nothing armed, picker left open). Picking (place OR change) makes sure
|
||||
// the plant renders and drops it into this garden's tray for quick reuse.
|
||||
function onPickPlant(plant: Plant) {
|
||||
function onPickPlant(plant: Plant, lot?: SeedLot) {
|
||||
if (!canEdit) return // defense in depth: viewers can't reach the picker anyway
|
||||
ensurePlant(plant)
|
||||
addToTray(plant)
|
||||
if (picker === 'change' && selectedPlop) {
|
||||
updatePlanting.mutate({ id: selectedPlop.id, version: selectedPlop.version, plantId: plant.id })
|
||||
} else {
|
||||
setArmedPlant(plant) // 'place' mode: arm for repeat placement
|
||||
setArmedPlant(plant, lot?.id ?? null) // 'place' mode: arm for repeat placement
|
||||
}
|
||||
setPicker(null)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ import { CategoryChips } from '@/components/plants/CategoryChips'
|
||||
import { PlantCard } from '@/components/plants/PlantCard'
|
||||
import { PlantFormModal } from '@/components/plants/PlantFormModal'
|
||||
import { DeletePlantModal } from '@/components/plants/DeletePlantModal'
|
||||
import { SeedLotModal } from '@/components/plants/SeedLotModal'
|
||||
import { DeleteSeedLotModal } from '@/components/plants/DeleteSeedLotModal'
|
||||
import { PlantPicker } from '@/editor/PlantPicker'
|
||||
import { filterPlants, usePlants, type CategoryFilter, type Plant } from '@/lib/plants'
|
||||
import { lotsByPlant, useSeedLots, type SeedLot } from '@/lib/seedLots'
|
||||
import type { UnitPref } from '@/lib/units'
|
||||
import { usePageTitle } from '@/lib/usePageTitle'
|
||||
|
||||
@@ -19,6 +22,9 @@ type Dialog =
|
||||
| { kind: 'duplicate'; plant: Plant }
|
||||
| { kind: 'delete'; plant: Plant }
|
||||
| { kind: 'picker' }
|
||||
| { kind: 'addLot'; plant: Plant }
|
||||
| { kind: 'editLot'; plant: Plant; lot: SeedLot }
|
||||
| { kind: 'deleteLot'; lot: SeedLot }
|
||||
| null
|
||||
|
||||
// There's no per-user unit preference server-side (only per-garden), so the
|
||||
@@ -35,6 +41,8 @@ function loadUnit(): UnitPref {
|
||||
export function PlantsPage() {
|
||||
usePageTitle('Plants')
|
||||
const plants = usePlants()
|
||||
const seedLots = useSeedLots()
|
||||
const lots = useMemo(() => lotsByPlant(seedLots.data), [seedLots.data])
|
||||
const [unit, setUnit] = useState<UnitPref>(() => loadUnit())
|
||||
const [query, setQuery] = useState('')
|
||||
const [category, setCategory] = useState<CategoryFilter>('all')
|
||||
@@ -108,9 +116,13 @@ export function PlantsPage() {
|
||||
key={p.id}
|
||||
plant={p}
|
||||
unit={unit}
|
||||
lots={lots.get(p.id) ?? []}
|
||||
onEdit={() => setDialog({ kind: 'edit', plant: p })}
|
||||
onDelete={() => setDialog({ kind: 'delete', plant: p })}
|
||||
onDuplicate={() => setDialog({ kind: 'duplicate', plant: p })}
|
||||
onAddLot={() => setDialog({ kind: 'addLot', plant: p })}
|
||||
onEditLot={(lot) => setDialog({ kind: 'editLot', plant: p, lot })}
|
||||
onDeleteLot={(lot) => setDialog({ kind: 'deleteLot', lot })}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -121,6 +133,9 @@ export function PlantsPage() {
|
||||
{dialog?.kind === 'edit' && <PlantFormModal plant={dialog.plant} unit={unit} onClose={close} />}
|
||||
{dialog?.kind === 'duplicate' && <PlantFormModal template={dialog.plant} unit={unit} onClose={close} />}
|
||||
{dialog?.kind === 'delete' && <DeletePlantModal plant={dialog.plant} onClose={close} />}
|
||||
{dialog?.kind === 'addLot' && <SeedLotModal plant={dialog.plant} onClose={close} />}
|
||||
{dialog?.kind === 'editLot' && <SeedLotModal plant={dialog.plant} lot={dialog.lot} onClose={close} />}
|
||||
{dialog?.kind === 'deleteLot' && <DeleteSeedLotModal lot={dialog.lot} onClose={close} />}
|
||||
{dialog?.kind === 'picker' && (
|
||||
<PlantPicker
|
||||
unit={unit}
|
||||
|
||||
Reference in New Issue
Block a user