"use client"; import { useEffect, useState } from "react"; import { useApi } from "@/lib/useApi"; type Merchant = { id: number; name: string; }; type CategoryMapping = { id: number; merchantId: number; merchantName: string; rawCategoryPath: string; partCategoryId: number | null; partCategoryName?: string | null; }; // Matches the Spring MerchantCategoryMappingDto record type MerchantCategoryMappingDto = { id: number; merchantId: number; merchantName: string; rawCategoryPath: string; partCategoryId: number | null; partCategoryName?: string | null; }; export default function MerchantCategoryMappingPage() { const { get, post } = useApi(); const [merchants, setMerchants] = useState([]); const [selectedMerchantId, setSelectedMerchantId] = useState( null ); const [mappings, setMappings] = useState([]); const [loadingMerchants, setLoadingMerchants] = useState(false); const [loadingMappings, setLoadingMappings] = useState(false); const [savingId, setSavingId] = useState(null); const [error, setError] = useState(null); // 1) Load merchants for the dropdown useEffect(() => { setLoadingMerchants(true); setError(null); get("/api/admin/category-mappings/merchants") .then((data) => { setMerchants(data); if (data.length > 0 && selectedMerchantId == null) { setSelectedMerchantId(data[0].id); } }) .catch((err: any) => { console.error("Failed to load merchants", err); setError( err instanceof Error ? err.message : "Failed to load merchants" ); }) .finally(() => setLoadingMerchants(false)); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 2) Load mappings when merchant changes (THIS sends merchantId) useEffect(() => { if (selectedMerchantId == null) return; setLoadingMappings(true); setError(null); get( `/api/admin/category-mappings?merchantId=${selectedMerchantId}` ) .then((data) => { setMappings(data); }) .catch((err: any) => { console.error("Failed to load category mappings", err); setError( err instanceof Error ? err.message : "Failed to load category mappings" ); }) .finally(() => setLoadingMappings(false)); }, [selectedMerchantId]); // 3) Save a single mapping row (adjust endpoint if yours differs) const handleSaveMapping = async ( mappingId: number, updated: { partCategoryId: number | null } ) => { setSavingId(mappingId); setError(null); try { const updatedMapping = await post( `/api/admin/category-mappings/${mappingId}`, { partCategoryId: updated.partCategoryId, } ); setMappings((prev) => prev.map((m) => (m.id === mappingId ? { ...m, // map Java DTO fields back into your UI model merchantId: updatedMapping.merchantId, merchantName: updatedMapping.merchantName, rawCategoryPath: updatedMapping.rawCategoryPath, partCategoryId: updatedMapping.partCategoryId, partCategoryName: updatedMapping.partCategoryName, } : m)) ); } catch (err: any) { console.error("Failed to save mapping", err); setError( err instanceof Error ? err.message : "Failed to save mapping" ); } finally { setSavingId(null); } }; return (

Merchant Category Mapping

Merchant:
{error && (
{error}
)}
Category Mappings
{loadingMappings ? (
Loading mappings…
) : mappings.length === 0 ? (
No mappings found for this merchant.
) : ( {mappings.map((m) => ( ))}
Merchant Category Global Category Actions
{m.rawCategoryPath} {/* For now, simple text input for partCategoryId. You can swap this for a select populated from /api/admin/categories */} { const value = e.target.value ? Number(e.target.value) : null; setMappings((prev) => prev.map((row) => row.id === m.id ? { ...row, partCategoryId: value } : row ) ); }} /> {m.partCategoryName && (
{m.partCategoryName}
)}
)}
); }