From acf34c33d9c21f89be729b4ab33f40d24b91e0ff Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 15 Mar 2026 17:14:20 +0100 Subject: [PATCH] feat(05-02): add always-visible 4:3 image area with placeholders to ItemCard and CandidateCard - Replace conditional image rendering with always-present 4:3 aspect ratio area - Show category emoji centered on gray background when no image exists - Ensures consistent card heights across grid layouts Co-Authored-By: Claude Opus 4.6 (1M context) --- src/client/components/CandidateCard.tsx | 168 ++++++++++++------------ src/client/components/ItemCard.tsx | 168 +++++++++++++----------- 2 files changed, 177 insertions(+), 159 deletions(-) diff --git a/src/client/components/CandidateCard.tsx b/src/client/components/CandidateCard.tsx index 40223b3..fb2ea42 100644 --- a/src/client/components/CandidateCard.tsx +++ b/src/client/components/CandidateCard.tsx @@ -1,91 +1,95 @@ -import { formatWeight, formatPrice } from "../lib/formatters"; +import { formatPrice, formatWeight } from "../lib/formatters"; import { useUIStore } from "../stores/uiStore"; interface CandidateCardProps { - id: number; - name: string; - weightGrams: number | null; - priceCents: number | null; - categoryName: string; - categoryEmoji: string; - imageFilename: string | null; - threadId: number; - isActive: boolean; + id: number; + name: string; + weightGrams: number | null; + priceCents: number | null; + categoryName: string; + categoryEmoji: string; + imageFilename: string | null; + threadId: number; + isActive: boolean; } export function CandidateCard({ - id, - name, - weightGrams, - priceCents, - categoryName, - categoryEmoji, - imageFilename, - threadId, - isActive, + id, + name, + weightGrams, + priceCents, + categoryName, + categoryEmoji, + imageFilename, + threadId, + isActive, }: CandidateCardProps) { - const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel); - const openConfirmDeleteCandidate = useUIStore( - (s) => s.openConfirmDeleteCandidate, - ); - const openResolveDialog = useUIStore((s) => s.openResolveDialog); + const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel); + const openConfirmDeleteCandidate = useUIStore( + (s) => s.openConfirmDeleteCandidate, + ); + const openResolveDialog = useUIStore((s) => s.openResolveDialog); - return ( -
- {imageFilename && ( -
- {name} -
- )} -
-

- {name} -

-
- {weightGrams != null && ( - - {formatWeight(weightGrams)} - - )} - {priceCents != null && ( - - {formatPrice(priceCents)} - - )} - - {categoryEmoji} {categoryName} - -
-
- - - {isActive && ( - - )} -
-
-
- ); + return ( +
+
+ {imageFilename ? ( + {name} + ) : ( +
+ {categoryEmoji} +
+ )} +
+
+

+ {name} +

+
+ {weightGrams != null && ( + + {formatWeight(weightGrams)} + + )} + {priceCents != null && ( + + {formatPrice(priceCents)} + + )} + + {categoryEmoji} {categoryName} + +
+
+ + + {isActive && ( + + )} +
+
+
+ ); } diff --git a/src/client/components/ItemCard.tsx b/src/client/components/ItemCard.tsx index 3535fac..2074e12 100644 --- a/src/client/components/ItemCard.tsx +++ b/src/client/components/ItemCard.tsx @@ -1,86 +1,100 @@ -import { formatWeight, formatPrice } from "../lib/formatters"; +import { formatPrice, formatWeight } from "../lib/formatters"; import { useUIStore } from "../stores/uiStore"; interface ItemCardProps { - id: number; - name: string; - weightGrams: number | null; - priceCents: number | null; - categoryName: string; - categoryEmoji: string; - imageFilename: string | null; - onRemove?: () => void; + id: number; + name: string; + weightGrams: number | null; + priceCents: number | null; + categoryName: string; + categoryEmoji: string; + imageFilename: string | null; + onRemove?: () => void; } export function ItemCard({ - id, - name, - weightGrams, - priceCents, - categoryName, - categoryEmoji, - imageFilename, - onRemove, + id, + name, + weightGrams, + priceCents, + categoryName, + categoryEmoji, + imageFilename, + onRemove, }: ItemCardProps) { - const openEditPanel = useUIStore((s) => s.openEditPanel); + const openEditPanel = useUIStore((s) => s.openEditPanel); - return ( - - ); + return ( + + ); }