Build image / build-and-push (push) Successful in 17s
Gadfly on #105: cardActionClass now bakes in `inline-flex items-center`, so the PlantCard seed-lot toggle's own `flex items-center` conflicted (two display utilities in one plain-string className). Drop them — keep just `mr-auto gap-1.5`. Also made cardActions.ts use one consistent concatenation style instead of mixing concat + template literals. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ
117 lines
4.2 KiB
TypeScript
117 lines
4.2 KiB
TypeScript
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'
|
|
|
|
/**
|
|
* One catalog plant as a card: icon tile tinted with the plant's color, name,
|
|
* category + mature spacing (unit-aware), and actions. Built-ins are badged and
|
|
* offer only "Duplicate" (they're read-only); own plants add Edit/Delete.
|
|
*/
|
|
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">
|
|
<PlantIcon color={plant.color} icon={plant.icon} className="h-11 w-11 rounded-lg text-2xl" />
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="truncate font-semibold text-fg">{plant.name}</h3>
|
|
{builtin && (
|
|
<span className="shrink-0 rounded bg-border/60 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted">
|
|
Built-in
|
|
</span>
|
|
)}
|
|
</div>
|
|
<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
|
|
className="mt-1 h-4 w-4 shrink-0 rounded-full border border-black/10 dark:border-white/10"
|
|
style={{ backgroundColor: plant.color }}
|
|
title={plant.color}
|
|
/>
|
|
</div>
|
|
{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 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>
|
|
{!builtin && (
|
|
<button type="button" onClick={onEdit} className={cardActionClass}>
|
|
Edit
|
|
</button>
|
|
)}
|
|
{!builtin && (
|
|
<button type="button" onClick={onDelete} className={cardDangerClass}>
|
|
Delete
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|