From 6ccd2bd442429e43a0f06ddb74864d88066251b4 Mon Sep 17 00:00:00 2001 From: robbond Date: Tue, 25 Nov 2025 09:50:19 +0000 Subject: [PATCH] bulk importer --- src/app/admin/import/page.js | 208 +++++++++++++++++++++++++++++++++++ src/lib/data/bulk.json | 85 ++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 src/app/admin/import/page.js create mode 100644 src/lib/data/bulk.json diff --git a/src/app/admin/import/page.js b/src/app/admin/import/page.js new file mode 100644 index 0000000..0b801a6 --- /dev/null +++ b/src/app/admin/import/page.js @@ -0,0 +1,208 @@ +"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 ( + + + Bulk JSON Import (Stub Data) + + + + {/* FILE UPLOAD */} +
+ + +
+ + {/* TEXTAREA */} +
+ +