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:
@@ -1,6 +1,10 @@
|
||||
import { useState } from 'react'
|
||||
import { PlantIcon } from './PlantIcon'
|
||||
import { LotStateChip, SeedLotList } from './SeedLotList'
|
||||
import { SourceLink } from './SourceLink'
|
||||
import { cardActionClass, cardDangerClass } from '@/components/ui/cardActions'
|
||||
import { CATEGORY_LABELS, isBuiltin, type Plant } from '@/lib/plants'
|
||||
import { formatQuantity, summarizeLots, type SeedLot } from '@/lib/seedLots'
|
||||
import { formatSpacing, type UnitPref } from '@/lib/units'
|
||||
|
||||
/**
|
||||
@@ -11,17 +15,30 @@ import { formatSpacing, type UnitPref } from '@/lib/units'
|
||||
export function PlantCard({
|
||||
plant,
|
||||
unit,
|
||||
lots,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onDuplicate,
|
||||
onAddLot,
|
||||
onEditLot,
|
||||
onDeleteLot,
|
||||
}: {
|
||||
plant: Plant
|
||||
unit: UnitPref
|
||||
/** This plant's purchases. A lot may reference a built-in, so even a built-in
|
||||
* card can carry seed. */
|
||||
lots: SeedLot[]
|
||||
onEdit: () => void
|
||||
onDelete: () => void
|
||||
onDuplicate: () => void
|
||||
onAddLot: () => void
|
||||
onEditLot: (lot: SeedLot) => void
|
||||
onDeleteLot: (lot: SeedLot) => void
|
||||
}) {
|
||||
const builtin = isBuiltin(plant)
|
||||
const [showLots, setShowLots] = useState(false)
|
||||
const summary = summarizeLots(lots)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col rounded-xl border border-border bg-surface">
|
||||
<div className="flex items-start gap-3 p-4">
|
||||
@@ -38,6 +55,12 @@ export function PlantCard({
|
||||
<p className="mt-0.5 text-sm text-muted">
|
||||
{CATEGORY_LABELS[plant.category]} · {formatSpacing(plant.spacingCm, unit)} spacing
|
||||
</p>
|
||||
{(plant.vendor || plant.sourceUrl) && (
|
||||
<p className="mt-0.5 flex flex-wrap items-center gap-1.5 text-xs text-muted">
|
||||
{plant.vendor && <span>{plant.vendor}</span>}
|
||||
<SourceLink url={plant.sourceUrl} />
|
||||
</p>
|
||||
)}
|
||||
{plant.notes && <p className="mt-1 line-clamp-2 text-xs text-muted">{plant.notes}</p>}
|
||||
</div>
|
||||
<span
|
||||
@@ -46,7 +69,34 @@ export function PlantCard({
|
||||
title={plant.color}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-1 border-t border-border px-2 py-1.5">
|
||||
{showLots && (
|
||||
<div className="border-t border-border px-3 py-2">
|
||||
<SeedLotList lots={lots} canEdit onAdd={onAddLot} onEdit={onEditLot} onDelete={onDeleteLot} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-end gap-1 border-t border-border px-2 py-1.5">
|
||||
{/* The seed count sits with the actions rather than in the body: it's
|
||||
what you scan for down a list of twenty packets, so it wants a fixed
|
||||
place on the card. */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowLots((v) => !v)}
|
||||
className={`${cardActionClass} mr-auto flex items-center gap-1.5`}
|
||||
aria-expanded={showLots}
|
||||
>
|
||||
{lots.length === 0 ? (
|
||||
<span className="text-muted">No seed</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="tabular-nums">
|
||||
{formatQuantity(summary.remaining)}
|
||||
{summary.unit ? ` ${summary.unit}` : ''} left
|
||||
</span>
|
||||
<LotStateChip state={summary.state} />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button type="button" onClick={onDuplicate} className={cardActionClass}>
|
||||
Duplicate
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user