"use client"; import Spinner from "@/app/components/Spinner/Spinner"; import { DataRowBase } from "@/app/features/DataTable/types"; import { setError as setAdvancedSearchError, setStatus, } from "@/app/redux/advanedSearch/advancedSearchSlice"; import { selectError, selectFilters, selectPagination, selectSort, selectStatus, } from "@/app/redux/advanedSearch/selectors"; import { AppDispatch } from "@/app/redux/store"; import { Alert, Box, Chip, Divider, List, ListItem, Typography, } from "@mui/material"; import { useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; type ResourceRow = DataRowBase & Record; type FilterValue = | string | { operator?: string; value: string; }; interface AdminResourceListProps { title: string; endpoint: string; responseCollectionKeys?: string[]; primaryLabelKeys: string[]; chipKeys?: string[]; excludeKeys?: string[]; filterOverrides?: Record; } const DEFAULT_COLLECTION_KEYS = ["data", "items"]; const ensureRowId = ( row: Record, fallbackId: number ): ResourceRow => { const currentId = row.id; if (typeof currentId === "number") { return row as ResourceRow; } const numericId = Number(currentId); if (!Number.isNaN(numericId) && numericId !== 0) { return { ...row, id: numericId } as ResourceRow; } return { ...row, id: fallbackId } as ResourceRow; }; const resolveCollection = ( payload: Record, preferredKeys: string[] = [] ) => { for (const key of [...preferredKeys, ...DEFAULT_COLLECTION_KEYS]) { const maybeCollection = payload?.[key]; if (Array.isArray(maybeCollection)) { return maybeCollection as Record[]; } } if (Array.isArray(payload)) { return payload as Record[]; } return []; }; const AdminResourceList = ({ title, endpoint, responseCollectionKeys = [], primaryLabelKeys, chipKeys = [], excludeKeys = [], }: AdminResourceListProps) => { const dispatch = useDispatch(); const filters = useSelector(selectFilters); const pagination = useSelector(selectPagination); const sort = useSelector(selectSort); const status = useSelector(selectStatus); const errorMessage = useSelector(selectError); const [rows, setRows] = useState([]); const normalizedTitle = title.toLowerCase(); const excludedKeys = useMemo(() => { const baseExcluded = new Set(["id", ...primaryLabelKeys, ...chipKeys]); excludeKeys.forEach(key => baseExcluded.add(key)); return Array.from(baseExcluded); }, [primaryLabelKeys, chipKeys, excludeKeys]); const getPrimaryLabel = (row: ResourceRow) => { for (const key of primaryLabelKeys) { if (row[key]) { return String(row[key]); } } return `${title} #${row.id}`; }; const getMetaChips = (row: ResourceRow) => chipKeys .filter(key => row[key]) .map(key => ({ key, value: String(row[key]), })); const getSecondaryDetails = (row: ResourceRow) => Object.entries(row).filter(([key]) => !excludedKeys.includes(key)); const resolvedCollectionKeys = useMemo( () => [...responseCollectionKeys], [responseCollectionKeys] ); useEffect(() => { const fetchResources = async () => { dispatch(setStatus("loading")); dispatch(setAdvancedSearchError(null)); try { const response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filters, pagination, sort, }), }); if (!response.ok) { dispatch( setAdvancedSearchError(`Failed to fetch ${normalizedTitle}`) ); setRows([]); return; } const backendData = await response.json(); const collection = resolveCollection( backendData, resolvedCollectionKeys ); const nextRows = collection.map((item, index) => ensureRowId(item, index + 1) ); setRows(nextRows); dispatch(setStatus("succeeded")); } catch (error) { dispatch( setAdvancedSearchError( error instanceof Error ? error.message : "Unknown error" ) ); setRows([]); } }; fetchResources(); }, [ dispatch, endpoint, filters, pagination, sort, resolvedCollectionKeys, normalizedTitle, ]); return ( {title} {status === "loading" && ( {`Loading ${normalizedTitle}...`} )} {status === "failed" && ( {errorMessage || `Failed to load ${normalizedTitle}`} )} {!rows.length && status === "succeeded" && ( {`No ${normalizedTitle} found.`} )} {rows.length > 0 && ( {rows.map(row => { const chips = getMetaChips(row); const secondary = getSecondaryDetails(row); return ( {getPrimaryLabel(row)} ID: {row.id} {chips.length > 0 && ( {chips.map(chip => ( ))} )} {secondary.length > 0 && ( {secondary .map(([key, value]) => `${key}: ${String(value)}`) .join(" • ")} )} ); })} )} ); }; export default AdminResourceList;