feat(06-03): update display components to use categoryIcon with LucideIcon

- Rename categoryEmoji to categoryIcon in ItemCard, CandidateCard, ThreadCard, ItemPicker
- Import and render LucideIcon at appropriate sizes (36px placeholder, 14-16px badges)
- Update hook interfaces to match server API (categoryIcon instead of categoryEmoji)
- Rename iconData.ts to iconData.tsx (contains JSX)
- Update useCategories mutation type to use icon instead of emoji

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 17:57:21 +01:00
parent 59d1c891f9
commit 615c8944c4
10 changed files with 25 additions and 21 deletions

View File

@@ -1,4 +1,5 @@
import { formatPrice, formatWeight } from "../lib/formatters"; import { formatPrice, formatWeight } from "../lib/formatters";
import { LucideIcon } from "../lib/iconData";
import { useUIStore } from "../stores/uiStore"; import { useUIStore } from "../stores/uiStore";
interface CandidateCardProps { interface CandidateCardProps {
@@ -7,7 +8,7 @@ interface CandidateCardProps {
weightGrams: number | null; weightGrams: number | null;
priceCents: number | null; priceCents: number | null;
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
imageFilename: string | null; imageFilename: string | null;
threadId: number; threadId: number;
isActive: boolean; isActive: boolean;
@@ -19,7 +20,7 @@ export function CandidateCard({
weightGrams, weightGrams,
priceCents, priceCents,
categoryName, categoryName,
categoryEmoji, categoryIcon,
imageFilename, imageFilename,
threadId, threadId,
isActive, isActive,
@@ -41,7 +42,7 @@ export function CandidateCard({
/> />
) : ( ) : (
<div className="w-full h-full flex flex-col items-center justify-center"> <div className="w-full h-full flex flex-col items-center justify-center">
<span className="text-3xl">{categoryEmoji}</span> <LucideIcon name={categoryIcon} size={36} className="text-gray-400" />
</div> </div>
)} )}
</div> </div>
@@ -61,7 +62,7 @@ export function CandidateCard({
</span> </span>
)} )}
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-50 text-gray-600"> <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-50 text-gray-600">
{categoryEmoji} {categoryName} <LucideIcon name={categoryIcon} size={14} className="inline-block mr-1 text-gray-500" /> {categoryName}
</span> </span>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">

View File

@@ -1,4 +1,5 @@
import { formatPrice, formatWeight } from "../lib/formatters"; import { formatPrice, formatWeight } from "../lib/formatters";
import { LucideIcon } from "../lib/iconData";
import { useUIStore } from "../stores/uiStore"; import { useUIStore } from "../stores/uiStore";
interface ItemCardProps { interface ItemCardProps {
@@ -7,7 +8,7 @@ interface ItemCardProps {
weightGrams: number | null; weightGrams: number | null;
priceCents: number | null; priceCents: number | null;
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
imageFilename: string | null; imageFilename: string | null;
onRemove?: () => void; onRemove?: () => void;
} }
@@ -18,7 +19,7 @@ export function ItemCard({
weightGrams, weightGrams,
priceCents, priceCents,
categoryName, categoryName,
categoryEmoji, categoryIcon,
imageFilename, imageFilename,
onRemove, onRemove,
}: ItemCardProps) { }: ItemCardProps) {
@@ -71,7 +72,7 @@ export function ItemCard({
/> />
) : ( ) : (
<div className="w-full h-full flex flex-col items-center justify-center"> <div className="w-full h-full flex flex-col items-center justify-center">
<span className="text-3xl">{categoryEmoji}</span> <LucideIcon name={categoryIcon} size={36} className="text-gray-400" />
</div> </div>
)} )}
</div> </div>
@@ -91,7 +92,7 @@ export function ItemCard({
</span> </span>
)} )}
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-50 text-gray-600"> <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-50 text-gray-600">
{categoryEmoji} {categoryName} <LucideIcon name={categoryIcon} size={14} className="inline-block mr-1 text-gray-500" /> {categoryName}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -3,6 +3,7 @@ import { SlideOutPanel } from "./SlideOutPanel";
import { useItems } from "../hooks/useItems"; import { useItems } from "../hooks/useItems";
import { useSyncSetupItems } from "../hooks/useSetups"; import { useSyncSetupItems } from "../hooks/useSetups";
import { formatWeight, formatPrice } from "../lib/formatters"; import { formatWeight, formatPrice } from "../lib/formatters";
import { LucideIcon } from "../lib/iconData";
interface ItemPickerProps { interface ItemPickerProps {
setupId: number; setupId: number;
@@ -51,7 +52,7 @@ export function ItemPicker({
number, number,
{ {
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
items: NonNullable<typeof items>; items: NonNullable<typeof items>;
} }
>(); >();
@@ -64,7 +65,7 @@ export function ItemPicker({
} else { } else {
grouped.set(item.categoryId, { grouped.set(item.categoryId, {
categoryName: item.categoryName, categoryName: item.categoryName,
categoryEmoji: item.categoryEmoji, categoryIcon: item.categoryIcon,
items: [item], items: [item],
}); });
} }
@@ -83,10 +84,10 @@ export function ItemPicker({
</div> </div>
) : ( ) : (
Array.from(grouped.entries()).map( Array.from(grouped.entries()).map(
([categoryId, { categoryName, categoryEmoji, items: catItems }]) => ( ([categoryId, { categoryName, categoryIcon, items: catItems }]) => (
<div key={categoryId} className="mb-4"> <div key={categoryId} className="mb-4">
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2"> <h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">
{categoryEmoji} {categoryName} <LucideIcon name={categoryIcon} size={16} className="inline-block mr-1 text-gray-500" /> {categoryName}
</h3> </h3>
<div className="space-y-1"> <div className="space-y-1">
{catItems.map((item) => ( {catItems.map((item) => (

View File

@@ -1,5 +1,6 @@
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { formatPrice } from "../lib/formatters"; import { formatPrice } from "../lib/formatters";
import { LucideIcon } from "../lib/iconData";
interface ThreadCardProps { interface ThreadCardProps {
id: number; id: number;
@@ -10,7 +11,7 @@ interface ThreadCardProps {
createdAt: string; createdAt: string;
status: "active" | "resolved"; status: "active" | "resolved";
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
} }
function formatDate(iso: string): string { function formatDate(iso: string): string {
@@ -36,7 +37,7 @@ export function ThreadCard({
createdAt, createdAt,
status, status,
categoryName, categoryName,
categoryEmoji, categoryIcon,
}: ThreadCardProps) { }: ThreadCardProps) {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -66,7 +67,7 @@ export function ThreadCard({
</div> </div>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700"> <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700">
{categoryEmoji} {categoryName} <LucideIcon name={categoryIcon} size={16} className="inline-block mr-1 text-gray-500" /> {categoryName}
</span> </span>
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-50 text-purple-700"> <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-purple-50 text-purple-700">
{candidateCount} {candidateCount === 1 ? "candidate" : "candidates"} {candidateCount} {candidateCount === 1 ? "candidate" : "candidates"}

View File

@@ -29,7 +29,7 @@ export function useUpdateCategory() {
}: { }: {
id: number; id: number;
name?: string; name?: string;
emoji?: string; icon?: string;
}) => apiPut<Category>(`/api/categories/${id}`, data), }) => apiPut<Category>(`/api/categories/${id}`, data),
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["categories"] }); queryClient.invalidateQueries({ queryKey: ["categories"] });

View File

@@ -14,7 +14,7 @@ interface ItemWithCategory {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
} }
export function useItems() { export function useItems() {

View File

@@ -23,7 +23,7 @@ interface SetupItemWithCategory {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
} }
interface SetupWithItems { interface SetupWithItems {

View File

@@ -8,7 +8,7 @@ interface ThreadListItem {
resolvedCandidateId: number | null; resolvedCandidateId: number | null;
categoryId: number; categoryId: number;
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
candidateCount: number; candidateCount: number;
@@ -29,7 +29,7 @@ interface CandidateWithCategory {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
} }
interface ThreadWithCandidates { interface ThreadWithCandidates {

View File

@@ -4,7 +4,7 @@ import { apiGet } from "../lib/api";
interface CategoryTotals { interface CategoryTotals {
categoryId: number; categoryId: number;
categoryName: string; categoryName: string;
categoryEmoji: string; categoryIcon: string;
totalWeight: number; totalWeight: number;
totalCost: number; totalCost: number;
itemCount: number; itemCount: number;