feat(29-02): update ComparisonTable, CatalogSearchOverlay, ImageUpload

Replace object-cover with GearImage across ComparisonTable,
CatalogSearchOverlay (2 instances), and ImageUpload preview.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 20:00:46 +02:00
parent 05c09182fd
commit 91846b5ca2
3 changed files with 41 additions and 13 deletions

View File

@@ -7,6 +7,7 @@ import { useFormatters } from "../hooks/useFormatters";
import { useGlobalItems } from "../hooks/useGlobalItems"; import { useGlobalItems } from "../hooks/useGlobalItems";
import { useTags } from "../hooks/useTags"; import { useTags } from "../hooks/useTags";
import { useUIStore } from "../stores/uiStore"; import { useUIStore } from "../stores/uiStore";
import { GearImage } from "./GearImage";
import { ManualEntryForm } from "./ManualEntryForm"; import { ManualEntryForm } from "./ManualEntryForm";
type ViewMode = "grid" | "list"; type ViewMode = "grid" | "list";
@@ -626,15 +627,21 @@ function GridCard({ item, onAdd, onCardClick, weight, price }: CardProps) {
className="bg-white rounded-xl border border-gray-100 overflow-hidden cursor-pointer hover:border-gray-200 hover:shadow-sm transition-all" className="bg-white rounded-xl border border-gray-100 overflow-hidden cursor-pointer hover:border-gray-200 hover:shadow-sm transition-all"
onClick={onCardClick} onClick={onCardClick}
> >
<div className="aspect-[4/3] bg-gray-50"> <div
className="aspect-[4/3] overflow-hidden"
style={{
backgroundColor: item.imageUrl
? ((item as Record<string, unknown>).dominantColor as string) || "#f3f4f6"
: undefined,
}}
>
{item.imageUrl ? ( {item.imageUrl ? (
<img <GearImage
src={item.imageUrl} src={item.imageUrl}
alt={`${item.brand} ${item.model}`} alt={`${item.brand} ${item.model}`}
className="w-full h-full object-cover"
/> />
) : ( ) : (
<div className="w-full h-full flex items-center justify-center"> <div className="w-full h-full bg-gray-50 flex items-center justify-center">
<svg <svg
className="w-9 h-9 text-gray-300" className="w-9 h-9 text-gray-300"
fill="none" fill="none"
@@ -696,15 +703,21 @@ function ListRow({ item, onAdd, onCardClick, weight, price }: CardProps) {
onClick={onCardClick} onClick={onCardClick}
> >
{/* Thumbnail */} {/* Thumbnail */}
<div className="w-12 h-12 rounded-lg bg-gray-50 shrink-0 overflow-hidden"> <div
className="w-12 h-12 rounded-lg shrink-0 overflow-hidden"
style={{
backgroundColor: item.imageUrl
? ((item as Record<string, unknown>).dominantColor as string) || "#f3f4f6"
: undefined,
}}
>
{item.imageUrl ? ( {item.imageUrl ? (
<img <GearImage
src={item.imageUrl} src={item.imageUrl}
alt={`${item.brand} ${item.model}`} alt={`${item.brand} ${item.model}`}
className="w-full h-full object-cover"
/> />
) : ( ) : (
<div className="w-full h-full flex items-center justify-center"> <div className="w-full h-full bg-gray-50 flex items-center justify-center">
<svg <svg
className="w-5 h-5 text-gray-300" className="w-5 h-5 text-gray-300"
fill="none" fill="none"

View File

@@ -4,6 +4,7 @@ import type { CandidateDelta } from "../hooks/useImpactDeltas";
import { LucideIcon } from "../lib/iconData"; import { LucideIcon } from "../lib/iconData";
import { useUIStore } from "../stores/uiStore"; import { useUIStore } from "../stores/uiStore";
import { RankBadge } from "./CandidateListItem"; import { RankBadge } from "./CandidateListItem";
import { GearImage } from "./GearImage";
import { ImpactDeltaBadge } from "./ImpactDeltaBadge"; import { ImpactDeltaBadge } from "./ImpactDeltaBadge";
interface CandidateWithCategory { interface CandidateWithCategory {
@@ -114,12 +115,18 @@ export function ComparisonTable({
key: "image", key: "image",
label: "Image", label: "Image",
render: (c) => ( render: (c) => (
<div className="w-12 h-12 rounded-lg overflow-hidden bg-gray-50 flex items-center justify-center"> <div
className="w-12 h-12 rounded-lg overflow-hidden flex items-center justify-center"
style={{
backgroundColor: c.imageUrl
? ((c as Record<string, unknown>).dominantColor as string) || "#f3f4f6"
: undefined,
}}
>
{c.imageUrl ? ( {c.imageUrl ? (
<img <GearImage
src={c.imageUrl} src={c.imageUrl}
alt={c.name} alt={c.name}
className="w-full h-full object-cover"
/> />
) : ( ) : (
<LucideIcon <LucideIcon

View File

@@ -1,9 +1,11 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { apiUpload } from "../lib/api"; import { apiUpload } from "../lib/api";
import { GearImage, imageContainerBg } from "./GearImage";
interface ImageUploadProps { interface ImageUploadProps {
value: string | null; value: string | null;
imageUrl?: string | null; imageUrl?: string | null;
dominantColor?: string | null;
onChange: (filename: string | null) => void; onChange: (filename: string | null) => void;
} }
@@ -13,6 +15,7 @@ const ACCEPTED_TYPES = ["image/jpeg", "image/png", "image/webp"];
export function ImageUpload({ export function ImageUpload({
value: _value, value: _value,
imageUrl, imageUrl,
dominantColor,
onChange, onChange,
}: ImageUploadProps) { }: ImageUploadProps) {
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
@@ -70,13 +73,18 @@ export function ImageUpload({
<div <div
onClick={() => inputRef.current?.click()} onClick={() => inputRef.current?.click()}
className="relative w-full aspect-[4/3] rounded-xl overflow-hidden cursor-pointer group" className="relative w-full aspect-[4/3] rounded-xl overflow-hidden cursor-pointer group"
style={{
backgroundColor: displayUrl
? imageContainerBg(dominantColor)
: undefined,
}}
> >
{displayUrl ? ( {displayUrl ? (
<> <>
<img <GearImage
src={displayUrl} src={displayUrl}
alt="Item" alt="Item"
className="w-full h-full object-cover" dominantColor={dominantColor}
/> />
{/* Remove button */} {/* Remove button */}
<button <button