diff --git a/packages/ra-supabase-ui-materialui/src/guessers/ListGuesser.tsx b/packages/ra-supabase-ui-materialui/src/guessers/ListGuesser.tsx index d8c5890..74d8903 100644 --- a/packages/ra-supabase-ui-materialui/src/guessers/ListGuesser.tsx +++ b/packages/ra-supabase-ui-materialui/src/guessers/ListGuesser.tsx @@ -8,6 +8,7 @@ import { ListView, InferredElement, listFieldTypes, + editFieldTypes, } from 'react-admin'; import type { ListProps, ListViewProps } from 'react-admin'; import { capitalize, singularize } from 'inflection'; @@ -56,6 +57,9 @@ export const ListGuesserView = ( const { data: schema, error, isPending } = useAPISchema(); const resource = useResourceContext(); const [child, setChild] = React.useState(null); + const [filters, setFilters] = React.useState< + React.ReactElement[] | undefined + >(undefined); if (!resource) { throw new Error('ListGuesser must be used withing a ResourceContext'); } @@ -67,7 +71,6 @@ export const ListGuesserView = ( return; } const resourceDefinition = schema.definitions?.[resource]; - const requiredFields = resourceDefinition?.required || []; if (!resourceDefinition || !resourceDefinition.properties) { throw new Error( `The resource ${resource} is not defined in the API schema` @@ -76,7 +79,7 @@ export const ListGuesserView = ( if (!resourceDefinition || !resourceDefinition.properties) { return; } - const inferredInputs = Object.keys(resourceDefinition.properties).map( + const inferredFields = Object.keys(resourceDefinition.properties).map( (source: string) => inferElementFromType({ name: source, @@ -90,40 +93,76 @@ export const ListGuesserView = ( 'string' ? resourceDefinition.properties![source].type : 'string') as string, - requiredFields, }) ); - const inferredForm = new InferredElement( + const inferredTable = new InferredElement( listFieldTypes.table, null, - inferredInputs + inferredFields ); - setChild(inferredForm.getElement()); + setChild(inferredTable.getElement()); + + const rowFilters = + schema! + .paths![`/${resource}`].get!.parameters?.filter(obj => + obj['$ref'].includes('rowFilter') + ) + .map(obj => obj['$ref'].split('.').pop()) ?? []; + const inferredInputsForFilters = rowFilters.map(source => { + const field = resourceDefinition.properties![source]; + return inferElementFromType({ + name: source, + types: editFieldTypes, + description: field.description, + format: field.format, + type: field.type as string, + }); + }); + if (inferredInputsForFilters.length > 0) { + const filterElements = inferredInputsForFilters.map(inferredInput => + inferredInput.getElement() + ); + setFilters(filterElements.filter(el => el != null)); + } + if (!enableLog) return; - const representation = inferredForm.getRepresentation(); + const tableRepresentation = inferredTable.getRepresentation(); - const components = ['List'] - .concat( - Array.from( - new Set( - Array.from(representation.matchAll(/<([^/\s>]+)/g)) - .map(match => match[1]) - .filter(component => component !== 'span') - ) - ) - ) - .sort(); + const filterRepresentation = + inferredInputsForFilters.length > 0 + ? `const filters = [ +${inferredInputsForFilters + .map(inferredInput => ' ' + inferredInput.getRepresentation()) + .join(',\n')} +]; +` + : ''; + + const fieldComponents = Array.from( + tableRepresentation.matchAll(/<([^/\s>]+)/g) + ) + .map(match => match[1]) + .filter(component => component !== 'span'); + const filterComponents = Array.from( + filterRepresentation.matchAll(/<([^/\s>]+)/g) + ) + .map(match => match[1]) + .filter(component => component !== 'span'); + const components = Array.from( + new Set(['List', ...fieldComponents, ...filterComponents]) + ).sort(); // eslint-disable-next-line no-console console.log( `Guessed List: import { ${components.join(', ')} } from 'react-admin'; - + +${filterRepresentation} export const ${capitalize(singularize(resource))}List = () => ( - -${representation} + +${tableRepresentation} );` ); @@ -132,5 +171,9 @@ ${representation} if (isPending) return ; if (error) return

Error: {error.message}

; - return {child}; + return ( + + {child} + + ); }; diff --git a/packages/ra-supabase-ui-materialui/src/guessers/ShowGuesser.tsx b/packages/ra-supabase-ui-materialui/src/guessers/ShowGuesser.tsx index 9f8f080..6f26fa9 100644 --- a/packages/ra-supabase-ui-materialui/src/guessers/ShowGuesser.tsx +++ b/packages/ra-supabase-ui-materialui/src/guessers/ShowGuesser.tsx @@ -57,7 +57,7 @@ export const ShowGuesserView = ( if (!resourceDefinition || !resourceDefinition.properties) { return; } - const inferredInputs = Object.keys(resourceDefinition.properties).map( + const inferredFields = Object.keys(resourceDefinition.properties).map( (source: string) => inferElementFromType({ name: source, @@ -74,15 +74,15 @@ export const ShowGuesserView = ( requiredFields, }) ); - const inferredForm = new InferredElement( + const inferredLayout = new InferredElement( showFieldTypes.show, null, - inferredInputs + inferredFields ); - setChild(inferredForm.getElement()); + setChild(inferredLayout.getElement()); if (!enableLog) return; - const representation = inferredForm.getRepresentation(); + const representation = inferredLayout.getRepresentation(); const components = ['Show'] .concat( diff --git a/packages/ra-supabase/README.md b/packages/ra-supabase/README.md index a788e01..3944861 100644 --- a/packages/ra-supabase/README.md +++ b/packages/ra-supabase/README.md @@ -34,14 +34,14 @@ const App = () => ( export default App; ``` -This generates an admin app with working CRUD for all resources. +This generates a dataProvider, and authProvider, and an admin app with working CRUD for all resources. ![Demo](./assets/demo.png) The generated admin is fully functional: - All public tables are listed in the sidebar -- Lists are sortable and paginated +- Lists are paginated, sortable, and filterable - Creating, editing, and deleting records is possible - Forms use the correct input component based on the field type - Relationships are displayed as links in show views and as autocomplete inputs in edit views diff --git a/packages/ra-supabase/assets/demo.png b/packages/ra-supabase/assets/demo.png index f9761e3..a70935b 100644 Binary files a/packages/ra-supabase/assets/demo.png and b/packages/ra-supabase/assets/demo.png differ