makearmy-app/components/buying-guide/LaserFinderPanel.tsx

171 lines
6.3 KiB
TypeScript
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 { useEffect, useMemo, useState } from "react";
import { dxGet } from "./dx";
type Entry = {
submission_id: string | number;
brand?: string | null;
model?: string | null;
title?: string | null;
price_min?: number | null;
price_max?: number | null;
laser_type?: string | null; // e.g., "CO2", "Diode", "Fiber" if present in your schema
bed_size_x?: number | null;
bed_size_y?: number | null;
};
export default function LaserFinderPanel() {
const [q, setQ] = useState("");
const [type, setType] = useState<string | "any">("any");
const [budget, setBudget] = useState<number | null>(null);
const [minBedX, setMinBedX] = useState<number | null>(null);
const [minBedY, setMinBedY] = useState<number | null>(null);
const [recs, setRecs] = useState<Entry[]>([]);
const [loading, setLoading] = useState(false);
const filter = useMemo(() => {
const f: any = {};
const ors: any[] = [];
if (q.trim()) {
ors.push({ brand: { _icontains: q } }, { model: { _icontains: q } }, { title: { _icontains: q } });
}
if (type !== "any") f.laser_type = { _eq: type };
if (budget != null) {
// either min/max under budget or range overlaps
ors.push(
{ price_min: { _lte: budget } },
{ price_max: { _lte: budget } },
{ _and: [{ price_min: { _lte: budget } }, { price_max: { _gte: 1 } }] }
);
}
if (minBedX != null) f.bed_size_x = { _gte: minBedX };
if (minBedY != null) f.bed_size_y = { _gte: minBedY };
if (ors.length) f._or = ors;
return f;
}, [q, type, budget, minBedX, minBedY]);
useEffect(() => {
(async () => {
setLoading(true);
try {
const data = await dxGet<Entry[]>("items/bg_entries", {
fields: "submission_id,brand,model,title,price_min,price_max,laser_type,bed_size_x,bed_size_y",
filter: JSON.stringify(filter),
limit: 50,
sort: "price_min,brand,model",
});
setRecs(data);
} finally {
setLoading(false);
}
})();
}, [filter]);
return (
<div className="space-y-4">
<div className="flex flex-wrap gap-3 items-end">
<div className="flex-1 min-w-[220px]">
<label className="text-xs text-zinc-400 mb-1 block">Search</label>
<input
className="w-full rounded-md border bg-background px-3 py-1.5"
placeholder="brand, model, title…"
value={q}
onChange={(e) => setQ(e.target.value)}
/>
</div>
<div className="flex flex-col">
<label className="text-xs text-zinc-400 mb-1">Type</label>
<select
className="rounded-md border bg-background px-2 py-1"
value={type}
onChange={(e) => setType(e.target.value as any)}
>
<option value="any">Any</option>
<option value="CO2">CO2</option>
<option value="Diode">Diode</option>
<option value="Fiber">Fiber</option>
</select>
</div>
<div className="flex flex-col w-[140px]">
<label className="text-xs text-zinc-400 mb-1">Budget (USD)</label>
<input
type="number"
className="rounded-md border bg-background px-2 py-1"
value={budget ?? ""}
onChange={(e) => setBudget(e.target.value ? Number(e.target.value) : null)}
/>
</div>
<div className="flex flex-col w-[120px]">
<label className="text-xs text-zinc-400 mb-1">Min Bed X (mm)</label>
<input
type="number"
className="rounded-md border bg-background px-2 py-1"
value={minBedX ?? ""}
onChange={(e) => setMinBedX(e.target.value ? Number(e.target.value) : null)}
/>
</div>
<div className="flex flex-col w-[120px]">
<label className="text-xs text-zinc-400 mb-1">Min Bed Y (mm)</label>
<input
type="number"
className="rounded-md border bg-background px-2 py-1"
value={minBedY ?? ""}
onChange={(e) => setMinBedY(e.target.value ? Number(e.target.value) : null)}
/>
</div>
</div>
{loading ? (
<div className="text-sm text-zinc-400">Searching</div>
) : recs.length === 0 ? (
<div className="text-sm text-zinc-400">No matching lasers.</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="text-left text-zinc-400">
<tr>
<th className="py-1 pr-3 font-normal">Laser</th>
<th className="py-1 pr-3 font-normal">Type</th>
<th className="py-1 pr-3 font-normal">Bed (mm)</th>
<th className="py-1 pr-3 font-normal">Price</th>
<th className="py-1 pr-3 font-normal"></th>
</tr>
</thead>
<tbody>
{recs.map(r => (
<tr key={r.submission_id} className="border-t">
<td className="py-1 pr-3">{r.brand} {r.model || r.title}</td>
<td className="py-1 pr-3">{r.laser_type || "—"}</td>
<td className="py-1 pr-3">
{(r.bed_size_x ?? "—")} × {(r.bed_size_y ?? "—")}
</td>
<td className="py-1 pr-3">
{(r.price_min || r.price_max)
? (r.price_min && r.price_max && r.price_min !== r.price_max
? `$${r.price_min.toLocaleString()}$${r.price_max.toLocaleString()}`
: `$${(r.price_min || r.price_max)!.toLocaleString()}`)
: "—"}
</td>
<td className="py-1 pr-3">
<a
className="underline"
href={`/buying-guide/${r.submission_id}`}
target="_blank"
rel="noopener noreferrer"
>
View
</a>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}