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();
};