import * as React from "react"; import { useState, useEffect } from "react"; import type { RaRecord } from "ra-core"; import { ListBase, getElementsFromRecords, InferredElement, useListContext, usePrevious, useResourceContext, } from "ra-core"; import { useLocation } from "react-router"; import type { ListProps, ListViewProps } from "@/components/admin/list"; import { ListView } from "@/components/admin/list"; import { capitalize, singularize } from "inflection"; import { DataTable } from "@/components/admin/data-table"; import { ArrayField } from "@/components/admin/array-field"; import { BadgeField } from "@/components/admin/badge-field"; import { ReferenceField } from "@/components/admin/reference-field"; import { SingleFieldList } from "@/components/admin/single-field-list"; import { ReferenceArrayField } from "@/components/admin/reference-array-field"; /** * A list page that automatically generates a DataTable from your data. * * Inspects the first record to infer field types and automatically creates appropriate columns. * Useful for rapid prototyping. Logs generated code to console. * * @see {@link https://marmelab.com/shadcn-admin-kit/docs/list/#scaffolding-a-list-page ListGuesser documentation} * * @example * import { Admin, ListGuesser } from '@/components/admin'; * import { Resource } from 'ra-core'; * import { dataProvider } from './dataProvider'; * * const App = () => ( * * // ... * * * ); */ export const ListGuesser = ( props: Omit & { enableLog?: boolean }, ) => { const { debounce, disableAuthentication, disableSyncWithLocation, exporter, filter, filterDefaultValues, perPage, resource, sort, ...rest } = props; // force a rerender of this component when any list parameter changes // otherwise the ListBase won't be rerendered when the sort changes // and the following check won't be performed useLocation(); // keep previous data, unless the resource changes const resourceFromContext = useResourceContext(props); const previousResource = usePrevious(resourceFromContext); const keepPreviousData = previousResource === resourceFromContext; return ( debounce={debounce} disableAuthentication={disableAuthentication} disableSyncWithLocation={disableSyncWithLocation} exporter={exporter} filter={filter} filterDefaultValues={filterDefaultValues} perPage={perPage} resource={resource} queryOptions={{ placeholderData: (previousData) => keepPreviousData ? previousData : undefined, }} sort={sort} > ); }; const ListViewGuesser = ( props: Omit & { enableLog?: boolean }, ) => { const { data } = useListContext(); const resource = useResourceContext(); const [child, setChild] = useState(null); const { enableLog = process.env.NODE_ENV === "development", ...rest } = props; useEffect(() => { setChild(null); }, [resource]); useEffect(() => { if (data && data.length > 0 && !child) { const inferredElements = getElementsFromRecords(data, listFieldTypes); const inferredChild = new InferredElement( listFieldTypes.table, null, inferredElements, ); const inferredChildElement = inferredChild.getElement(); const representation = inferredChild.getRepresentation(); if (!resource) { throw new Error( "Cannot use outside of a ResourceContext", ); } if (!inferredChildElement || !representation) { return; } setChild(inferredChildElement); const components = ["List"] .concat( Array.from( new Set( Array.from(representation.matchAll(/<([^/\s\\.>]+)/g)) .map((match) => match[1]) .filter((component) => component !== "span"), ), ), ) .sort(); if (enableLog) { // eslint-disable-next-line no-console console.log( `Guessed List: ${components .map( (component) => `import { ${component} } from "@/components/admin/${kebabCase( component, )}";`, ) .join("\n")} export const ${capitalize(singularize(resource))}List = () => ( ${inferredChild.getRepresentation()} );`, ); } } }, [data, child, resource, enableLog]); return {child}; }; const listFieldTypes = { table: { component: (props: any) => { return ; }, representation: ( _props: any, children: { getRepresentation: () => string }[], ) => ` ${children .map((child) => ` ${child.getRepresentation()}`) .join("\n")} `, }, reference: { component: (props: any) => ( ), representation: (props: any) => ` `, }, array: { component: ({ children, ...props }: any) => { const childrenArray = React.Children.toArray(children); return ( 0 && React.isValidElement(childrenArray[0]) && (childrenArray[0].props as any).source } /> ); }, representation: (props: any, children: any) => ` `, }, referenceArray: { component: (props: any) => ( ), representation: (props: any) => ` `, }, string: { component: DataTable.Col, representation: (props: any) => ``, }, }; const kebabCase = (name: string) => { return name .replace(/([a-z])([A-Z])/g, "$1-$2") .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") .toLowerCase(); };