"use client" import { useEffect, useState, FormEvent } from "react" import { Gamepad2Icon, PlusIcon, PencilIcon, TrashIcon, Loader2, XIcon, } from "lucide-react" import { getDeviceColor } from "@/components/charts/EChartWrapper" interface DeviceItem { slug: string name: string deviceType: string image: string | null sortOrder: number totalBenchmarks?: number avgFps?: number | null gameCount?: number wattHours?: number | null tdpMax?: number | null } interface DeviceFormData { slug: string name: string deviceType: "handheld" | "console" image: string sortOrder: number wattHours: string tdpMax: string } export function HardwareClient() { const [devices, setDevices] = useState([]) const [loading, setLoading] = useState(true) const [showModal, setShowModal] = useState(false) const [editingDevice, setEditingDevice] = useState(null) const [form, setForm] = useState({ slug: "", name: "", deviceType: "handheld", image: "", sortOrder: 0, wattHours: "", tdpMax: "", }) const [saving, setSaving] = useState(false) const [deleting, setDeleting] = useState(false) const fetchDevices = async () => { setLoading(true) try { const res = await fetch("/api/hardware/stats") if (res.ok) { const data = await res.json() setDevices( (data as DeviceItem[]).map((item) => ({ ...item, image: item.image ?? null, })) ) } } catch { // ignore } finally { setLoading(false) } } useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect fetchDevices() }, []) const openCreate = () => { setEditingDevice(null) setForm({ slug: "", name: "", deviceType: "handheld", image: "", sortOrder: 0, wattHours: "", tdpMax: "", }) setShowModal(true) } const openEdit = (device: DeviceItem) => { setEditingDevice(device) setForm({ slug: device.slug, name: device.name, deviceType: device.deviceType as "handheld" | "console", image: device.image ?? "", sortOrder: device.sortOrder, wattHours: device.wattHours != null ? String(device.wattHours) : "", tdpMax: device.tdpMax != null ? String(device.tdpMax) : "", }) setShowModal(true) } const closeModal = () => { setShowModal(false) setEditingDevice(null) } const handleSubmit = async (e: FormEvent) => { e.preventDefault() setSaving(true) try { const body = editingDevice ? { name: form.name, deviceType: form.deviceType, image: form.image || null, sortOrder: form.sortOrder, wattHours: form.wattHours ? parseFloat(form.wattHours) : null, tdpMax: form.tdpMax ? parseFloat(form.tdpMax) : null, } : { slug: form.slug, name: form.name, deviceType: form.deviceType, image: form.image || null, sortOrder: form.sortOrder, wattHours: form.wattHours ? parseFloat(form.wattHours) : null, tdpMax: form.tdpMax ? parseFloat(form.tdpMax) : null, } const url = editingDevice ? `/api/hardware/${editingDevice.slug}` : "/api/hardware" const method = editingDevice ? "PATCH" : "POST" const res = await fetch(url, { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }) if (res.ok) { closeModal() await fetchDevices() } } finally { setSaving(false) } } const handleDelete = async (slug: string) => { if (!confirm("Are you sure you want to delete this device?")) return setDeleting(true) try { const res = await fetch(`/api/hardware/${slug}`, { method: "DELETE" }) if (res.ok) { await fetchDevices() } } finally { setDeleting(false) } } return (

Hardware

{loading ? (
) : devices.length === 0 ? (

No devices found

) : (
{devices.map((device, index) => { const color = getDeviceColor(index) return (

{device.name}

/{device.slug}

{device.deviceType}
{device.totalBenchmarks ?? 0} benchmarks
{(device.wattHours != null || device.tdpMax != null) && (
{device.wattHours != null && {device.wattHours} Wh} {device.tdpMax != null && {device.tdpMax}W TDP}
)}
) })}
)} {showModal && (

{editingDevice ? "Edit Device" : "Add Device"}

setForm((f) => ({ ...f, slug: e.target.value })) } disabled={!!editingDevice} required className="w-full px-3 py-2 rounded-md bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" />
setForm((f) => ({ ...f, name: e.target.value })) } required className="w-full px-3 py-2 rounded-md bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />
setForm((f) => ({ ...f, sortOrder: Number(e.target.value), })) } className="w-full px-3 py-2 rounded-md bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />
setForm((f) => ({ ...f, image: e.target.value })) } placeholder="https://..." className="w-full px-3 py-2 rounded-md bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />
setForm((f) => ({ ...f, wattHours: e.target.value }))} placeholder="e.g. 50.0" min="0" max="200" step="0.1" className="w-full px-3 py-2 rounded-md bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />

Watt-hours (used for battery life estimation)

setForm((f) => ({ ...f, tdpMax: e.target.value }))} placeholder="e.g. 15" min="0" max="100" step="0.1" className="w-full px-3 py-2 rounded-md bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />

Maximum TDP in watts (used for battery life estimation)

)}
) }