Files
rc-compat/src/app/admin/import/page.js
T
2025-11-25 09:50:19 +00:00

209 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { supabaseBrowser } from "@/lib/supabaseClient";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
// Optional helper: guess manufacturer from part ID prefix
function guessManufacturerFromSku(id = "") {
if (id.startsWith("TAM-")) return "Tamiya";
if (id.startsWith("TRA-")) return "Traxxas";
return null;
}
export default function ImportPage() {
const [jsonText, setJsonText] = useState("");
const [parsed, setParsed] = useState(null);
const [status, setStatus] = useState("");
const [importing, setImporting] = useState(false);
function handleFile(e) {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => setJsonText(reader.result);
reader.readAsText(file);
}
function preview() {
try {
const obj = JSON.parse(jsonText);
setParsed(obj);
setStatus("✅ JSON parsed successfully.");
} catch (err) {
setStatus("❌ Invalid JSON: " + err.message);
}
}
async function runImport() {
if (!parsed) {
setStatus("❌ No parsed JSON to import.");
return;
}
const { models = [], parts = [] } = parsed;
if (!Array.isArray(models) || !Array.isArray(parts)) {
setStatus("❌ JSON must contain `models` and `parts` arrays.");
return;
}
setImporting(true);
setStatus("Importing data…");
const modelSlugToId = {}; // map: slug (id in stub) -> DB uuid
// STEP 1: Insert models
for (const m of models) {
try {
const metadata = {
scale: m.scale || null,
categories: m.categories || [],
generation: m.generation ?? null,
slug: m.id, // keep the original slug
};
const { data, error } = await supabaseBrowser
.from("models")
.insert({
name: m.name,
manufacturer: m.brand || null,
category:
Array.isArray(m.categories) && m.categories.length > 0
? m.categories[0]
: null,
year: m.year || null,
description: null,
metadata,
})
.select()
.single();
if (error) {
setStatus(`❌ Error inserting model "${m.name}": ${error.message}`);
setImporting(false);
return;
}
modelSlugToId[m.id] = data.id;
} catch (err) {
setStatus(`❌ Exception inserting model "${m.name}": ${String(err)}`);
setImporting(false);
return;
}
}
// STEP 2: Insert parts and compatibility
for (const p of parts) {
try {
const metadata = {
upgrade: p.upgrade ?? false,
universalFit: p.universalFit ?? false,
};
const { data: partData, error: partErr } = await supabaseBrowser
.from("parts")
.insert({
name: p.name,
manufacturer: guessManufacturerFromSku(p.id),
category: p.category || null,
sku: p.id,
description: p.notes || null,
metadata,
})
.select()
.single();
if (partErr) {
setStatus(`❌ Error inserting part "${p.name}": ${partErr.message}`);
setImporting(false);
return;
}
// Compatibility from fitsModels
if (Array.isArray(p.fitsModels)) {
for (const slug of p.fitsModels) {
const model_id = modelSlugToId[slug];
if (!model_id) {
// model not found just warn, don't abort
console.warn("Unknown model slug in fitsModels:", slug);
continue;
}
const fit_type = "direct"; // default; could be expanded later
await supabaseBrowser.from("parts_compatible_models").insert({
part_id: partData.id,
model_id,
fit_type,
notes: p.notes || "",
});
}
}
} catch (err) {
setStatus(`❌ Exception inserting part "${p.name}": ${String(err)}`);
setImporting(false);
return;
}
}
setStatus("✅ Import complete!");
setImporting(false);
}
return (
<Card className="max-w-3xl mx-auto">
<CardHeader>
<CardTitle className="text-2xl">Bulk JSON Import (Stub Data)</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* FILE UPLOAD */}
<div>
<Label>Upload JSON File</Label>
<Input type="file" accept=".json" onChange={handleFile} />
</div>
{/* TEXTAREA */}
<div>
<Label>Or paste JSON</Label>
<Textarea
value={jsonText}
onChange={(e) => setJsonText(e.target.value)}
className="min-h-[200px] font-mono text-sm"
/>
</div>
{/* PREVIEW BUTTON */}
<Button onClick={preview} variant="secondary">
Preview JSON
</Button>
{/* PARSED PREVIEW */}
{parsed && (
<pre className="bg-gray-900 text-gray-200 p-4 rounded text-xs overflow-x-auto max-h-64">
{JSON.stringify(parsed, null, 2)}
</pre>
)}
{/* IMPORT BUTTON */}
<Button onClick={runImport} disabled={importing}>
{importing ? "Importing…" : "Import Data"}
</Button>
{/* STATUS */}
{status && (
<div className="p-3 bg-gray-100 border rounded text-sm whitespace-pre-wrap">
{status}
</div>
)}
</CardContent>
</Card>
);
}