- Create SetupCard component with name, item count, weight, cost display - Build setups list page with inline create form and grid layout - Build setup detail page with category-grouped items and sticky totals bar - Create ItemPicker component in SlideOutPanel with category-grouped checkboxes - Add onRemove prop to ItemCard for non-destructive setup item removal - Setup detail has delete confirmation dialog with collection safety note - Empty states for both setup list and setup detail pages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import { useState } from "react";
|
||
import { createFileRoute } from "@tanstack/react-router";
|
||
import { useSetups, useCreateSetup } from "../../hooks/useSetups";
|
||
import { SetupCard } from "../../components/SetupCard";
|
||
|
||
export const Route = createFileRoute("/setups/")({
|
||
component: SetupsListPage,
|
||
});
|
||
|
||
function SetupsListPage() {
|
||
const [newSetupName, setNewSetupName] = useState("");
|
||
const { data: setups, isLoading } = useSetups();
|
||
const createSetup = useCreateSetup();
|
||
|
||
function handleCreateSetup(e: React.FormEvent) {
|
||
e.preventDefault();
|
||
const name = newSetupName.trim();
|
||
if (!name) return;
|
||
createSetup.mutate(
|
||
{ name },
|
||
{ onSuccess: () => setNewSetupName("") },
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||
{/* Create setup form */}
|
||
<form onSubmit={handleCreateSetup} className="flex gap-2 mb-6">
|
||
<input
|
||
type="text"
|
||
value={newSetupName}
|
||
onChange={(e) => setNewSetupName(e.target.value)}
|
||
placeholder="New setup name..."
|
||
className="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||
/>
|
||
<button
|
||
type="submit"
|
||
disabled={!newSetupName.trim() || createSetup.isPending}
|
||
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white text-sm font-medium rounded-lg transition-colors"
|
||
>
|
||
{createSetup.isPending ? "Creating..." : "Create"}
|
||
</button>
|
||
</form>
|
||
|
||
{/* Loading skeleton */}
|
||
{isLoading && (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{[1, 2].map((i) => (
|
||
<div key={i} className="h-24 bg-gray-200 rounded-xl animate-pulse" />
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Empty state */}
|
||
{!isLoading && (!setups || setups.length === 0) && (
|
||
<div className="py-16 text-center">
|
||
<div className="max-w-md mx-auto">
|
||
<div className="text-5xl mb-4">🏕️</div>
|
||
<h2 className="text-xl font-semibold text-gray-900 mb-2">
|
||
No setups yet
|
||
</h2>
|
||
<p className="text-sm text-gray-500">
|
||
Create one to plan your loadout.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Setup grid */}
|
||
{!isLoading && setups && setups.length > 0 && (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{setups.map((setup) => (
|
||
<SetupCard
|
||
key={setup.id}
|
||
id={setup.id}
|
||
name={setup.name}
|
||
itemCount={setup.itemCount}
|
||
totalWeight={setup.totalWeight}
|
||
totalCost={setup.totalCost}
|
||
/>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|