adminjs

Custom Filter AdminJS (Component + Action)


I am trying to create a custom filter. I am on a table and want to sort according to fields on another table. ManyToMany relation between tables.

I also need to create a custom filter component, with matching design.

After a day of work, my working solution is in the answer, so if you have the same issue, it will save you time :)


Solution

  • Here is my solution :

    I linked the component from the ResourceOptions in the components section within a custom component who had already a customer list bundle :

    filter: AdminJS.bundle("../../components/generic/filter/FilterSublist"),
    

    Here is the custom style :

    //style so your custom components looks like the basic AdminJS component
    const customStyles = {
      menu: (provided, state) => ({
        ...provided,
        borderRadius: 0,
        backgroundColor: "#343F87",
      }),
    
      control: (provided, state) => ({
        ...provided,
        backgroundColor: "#343F87",
        borderRadius: 0,
        border: '1px solid #525C99',
      }),
    
      option: (provided, state) => ({
        ...provided,
        '&:hover': {
          backgroundColor: '#2E3974',
        },
        backgroundColor: "#343F87",
      }),
    }

    Here is the component :

    import {BasePropertyComponent, BasePropertyProps, ApiClient, useRecord} from "adminjs";
    import AsyncSelect from 'react-select/async';
    import React, {useState, useEffect} from "react";
    
    const FilterSublist = (props: BasePropertyProps) => {
      const [values, setValues] = useState<Array<{label: string, value: any}>>([]);
      const [defaultOptions, setDefaultOptions] = useState<Array<{label: string, value: any}>>([]);
      const api = new ApiClient()
    
      useEffect(() => {handleGetDefaultValues()}, [])
    
      const handleGetDefaultValues = async () => {
        const searchResults = await api.searchRecords({resourceId: "ServiceProviderAreaOfAction", query: ""})
        const filteredResults = searchResults.filter(r => r.params.id !== Number(values))
        setDefaultOptions(filteredResults.map(v => ({label: v.title, value: v.params.id})))
      }
    
      const handleSearch = async (inputValue: string, callback: (options: Array<{ label: string, value: any }>) => void) => {
        const searchResults = await api.searchRecords({resourceId: "ServiceProviderAreaOfAction", query: inputValue})
        const filteredResults = searchResults.filter(r => !values.map(v => Number(v.value.id)).includes(Number(r.params.id)))
        callback(filteredResults.map(v => ({label: v.title, value: v.params.id})))
      }
    
      //Store values in props.filter so they are sent with all the filters
      const handleChange = (a: Array<{label: string, value: any}>) => {
        setValues(a ? a : [])
          // @ts-ignore
          props.onChange(props.property.propertyPath, (a ? a.map(e => e.value).join(',') : undefined))
      }
    
      return (
        <div style={{marginBottom: 18}}>
          <p style={{fontSize: 12, marginBottom: 8}}>Area of action</p>
          {/* @ts-ignore */}
          <AsyncSelect className="AsyncSelectBlue" isMulti defaultOptions={defaultOptions} value={values} onChange={handleChange} loadOptions={handleSearch} styles={customStyles}/>
        </div>
      )
    }
    
    export default FilterSublist

    In the ResourceOptions again, I used a custom handler to list actions, and in that handler, I detected the custom filter, transformed it to an array of id, then deleted the custom filter field, let the normal filter do his job, and filtered the result with the array of id.

    I hope it helps :)