feat(01-03): add data hooks, utilities, UI store, and foundational components

- API fetch wrapper with error handling and multipart upload
- Weight/price formatters for display
- TanStack Query hooks for items, categories, and totals with cache invalidation
- Zustand UI store for panel and confirm dialog state
- TotalsBar, CategoryHeader, ItemCard, ConfirmDialog, ImageUpload components

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 22:44:48 +01:00
parent a5df33a2d8
commit b099a47eb4
11 changed files with 647 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
import { useUIStore } from "../stores/uiStore";
import { useDeleteItem } from "../hooks/useItems";
import { useItems } from "../hooks/useItems";
export function ConfirmDialog() {
const confirmDeleteItemId = useUIStore((s) => s.confirmDeleteItemId);
const closeConfirmDelete = useUIStore((s) => s.closeConfirmDelete);
const deleteItem = useDeleteItem();
const { data: items } = useItems();
if (confirmDeleteItemId == null) return null;
const item = items?.find((i) => i.id === confirmDeleteItemId);
const itemName = item?.name ?? "this item";
function handleDelete() {
if (confirmDeleteItemId == null) return;
deleteItem.mutate(confirmDeleteItemId, {
onSuccess: () => closeConfirmDelete(),
});
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div
className="absolute inset-0 bg-black/30"
onClick={closeConfirmDelete}
onKeyDown={(e) => {
if (e.key === "Escape") closeConfirmDelete();
}}
/>
<div className="relative bg-white rounded-xl shadow-lg p-6 max-w-sm mx-4 w-full">
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Delete Item
</h3>
<p className="text-sm text-gray-600 mb-6">
Are you sure you want to delete{" "}
<span className="font-medium">{itemName}</span>? This action cannot be
undone.
</p>
<div className="flex justify-end gap-3">
<button
type="button"
onClick={closeConfirmDelete}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
>
Cancel
</button>
<button
type="button"
onClick={handleDelete}
disabled={deleteItem.isPending}
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:opacity-50 rounded-lg transition-colors"
>
{deleteItem.isPending ? "Deleting..." : "Delete"}
</button>
</div>
</div>
</div>
);
}