new admin mapping logic and UI. see http://localhost:3000/gunbuilder/admin/category-mappings
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
|
||||
const API_BASE_URL =
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080";
|
||||
@@ -50,6 +50,29 @@ export default function CategoryMappingsPage() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
||||
const [showOnlyUnmapped, setShowOnlyUnmapped] = useState(false);
|
||||
|
||||
const sortedMappings = useMemo(() => {
|
||||
// Mapped rows first, then unmapped; within each group, alpha by rawCategory
|
||||
return [...mappings].sort((a, b) => {
|
||||
const aMapped = !!a.mappedPartRole;
|
||||
const bMapped = !!b.mappedPartRole;
|
||||
|
||||
if (aMapped !== bMapped) {
|
||||
return aMapped ? -1 : 1; // mapped first
|
||||
}
|
||||
|
||||
return a.rawCategory.localeCompare(b.rawCategory);
|
||||
});
|
||||
}, [mappings]);
|
||||
|
||||
const visibleMappings = useMemo(
|
||||
() =>
|
||||
showOnlyUnmapped
|
||||
? sortedMappings.filter((m) => !m.mappedPartRole)
|
||||
: sortedMappings,
|
||||
[sortedMappings, showOnlyUnmapped],
|
||||
);
|
||||
|
||||
// Load merchants
|
||||
useEffect(() => {
|
||||
@@ -232,10 +255,22 @@ export default function CategoryMappingsPage() {
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="mb-3 text-xs text-zinc-500">
|
||||
Mappings for <span className="text-zinc-300">{selectedMerchantName}</span>.{" "}
|
||||
Set a part role for each raw category. Unmapped categories will
|
||||
be skipped by the importer.
|
||||
<div className="mb-3 flex items-center justify-between gap-3 text-xs text-zinc-500">
|
||||
<div>
|
||||
Mappings for{" "}
|
||||
<span className="text-zinc-300">{selectedMerchantName}</span>.{" "}
|
||||
Set a part role for each raw category. Unmapped categories will
|
||||
be skipped by the importer.
|
||||
</div>
|
||||
<label className="inline-flex items-center gap-1 text-[0.7rem] text-zinc-400">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="h-3 w-3 rounded border-zinc-600 bg-zinc-900"
|
||||
checked={showOnlyUnmapped}
|
||||
onChange={(e) => setShowOnlyUnmapped(e.target.checked)}
|
||||
/>
|
||||
<span>Show only unmapped</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
@@ -244,10 +279,11 @@ export default function CategoryMappingsPage() {
|
||||
<tr className="border-b border-zinc-800 text-xs uppercase tracking-[0.16em] text-zinc-500">
|
||||
<th className="py-2 text-left pr-4">Raw Category</th>
|
||||
<th className="py-2 text-left pr-4">Mapped Part Role</th>
|
||||
<th className="py-2 text-left pr-4">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mappings.map((m) => (
|
||||
{visibleMappings.map((m) => (
|
||||
<tr
|
||||
key={m.rawCategory}
|
||||
className="border-b border-zinc-900 last:border-b-0"
|
||||
@@ -273,6 +309,17 @@ export default function CategoryMappingsPage() {
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td className="py-2 pr-4 align-top text-sm">
|
||||
{m.mappedPartRole ? (
|
||||
<span className="inline-flex items-center rounded-full bg-emerald-500/10 px-2 py-0.5 text-[0.7rem] font-medium text-emerald-300">
|
||||
Mapped
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center rounded-full bg-zinc-700/40 px-2 py-0.5 text-[0.7rem] font-medium text-zinc-300">
|
||||
Unmapped / Ignored
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
Reference in New Issue
Block a user