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:
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) => (
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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"] });
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user