update admin pages for models and parts

This commit is contained in:
2025-11-25 09:18:59 +00:00
parent 5412abe0fe
commit 0a938d5a6c
31 changed files with 3215 additions and 522 deletions
+167
View File
@@ -0,0 +1,167 @@
"use client";
import { use, useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { supabaseBrowser } from "@/lib/supabaseClient";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select";
export default function AddPartToModelPage({ params }) {
const { id: modelId } = use(params); // React 19 unwrap
const router = useRouter();
const [search, setSearch] = useState("");
const [results, setResults] = useState([]);
const [selectedPart, setSelectedPart] = useState(null);
const [fitType, setFitType] = useState("direct");
const [notes, setNotes] = useState("");
// Search parts live
useEffect(() => {
async function load() {
if (!search.trim()) {
setResults([]);
return;
}
const { data } = await supabaseBrowser
.from("parts")
.select("*")
.ilike("name", `%${search}%`)
.limit(20);
setResults(data || []);
}
load();
}, [search]);
async function save() {
if (!selectedPart) return;
await supabaseBrowser.from("parts_compatible_models").insert({
part_id: selectedPart.id,
model_id: modelId,
fit_type: fitType,
notes: notes || null,
});
router.push(`/admin/models/${modelId}`);
}
return (
<Card className="max-w-2xl mx-auto p-4">
<CardHeader>
<CardTitle className="text-2xl">Add Compatible Part</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* SEARCH */}
<div className="space-y-2">
<Label>Search Parts</Label>
<Input
placeholder="Enter part name…"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
{/* SEARCH RESULTS */}
{!selectedPart && results.length > 0 && (
<div className="border rounded divide-y">
{results.map((p) => (
<button
key={p.id}
onClick={() => setSelectedPart(p)}
className="w-full text-left p-3 hover:bg-gray-50"
>
<div className="font-medium">{p.name}</div>
<div className="text-sm text-gray-500">
{p.manufacturer || "-"} {p.category || "-"}
</div>
</button>
))}
</div>
)}
{/* SELECTED PART CARD */}
{selectedPart && (
<div className="p-4 border rounded bg-gray-50">
<div className="font-semibold">{selectedPart.name}</div>
<div className="text-sm text-gray-500">
{selectedPart.manufacturer || "-"} {" "}
{selectedPart.category || "-"}
</div>
<Button
variant="secondary"
size="sm"
className="mt-2"
onClick={() => setSelectedPart(null)}
>
Change
</Button>
</div>
)}
{/* FIT TYPE + NOTES */}
{selectedPart && (
<>
<div className="space-y-2">
<Label>Fit Type</Label>
<Select value={fitType} onValueChange={setFitType}>
<SelectTrigger>
<SelectValue placeholder="Choose Fit Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="direct">Direct Fit</SelectItem>
<SelectItem value="mod_required">
Modification Required
</SelectItem>
<SelectItem value="not_recommended">
Not Recommended
</SelectItem>
<SelectItem value="unknown">Unknown</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Notes (optional)</Label>
<Textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
className="min-h-[100px]"
/>
</div>
{/* SAVE */}
<Button onClick={save} className="mt-4">
Save Compatibility
</Button>
</>
)}
<Button
variant="secondary"
className="mt-4"
onClick={() => router.back()}
>
Cancel
</Button>
</CardContent>
</Card>
);
}