update admin pages for models and parts
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user