diff --git a/who-metrics-ui/src/components/RepositoriesTable.tsx b/who-metrics-ui/src/components/RepositoriesTable.tsx index 0248c7d..03a728d 100644 --- a/who-metrics-ui/src/components/RepositoriesTable.tsx +++ b/who-metrics-ui/src/components/RepositoriesTable.tsx @@ -41,7 +41,7 @@ function inputStopPropagation(event: React.KeyboardEvent) { } type Filter = { - repositoryName?: string; + repositoryName?: Record; licenseName?: Record; collaboratorsCount?: Array; watchersCount?: Array; @@ -52,6 +52,31 @@ type Filter = { forksCount?: Array; }; +type SelectOption = { + label: string | number; + value: string | number; +}; + +// This selects a field to populate a dropdown with +const dropdownOptions = (field: keyof Repo, filter = ''): SelectOption[] => + Array.from(new Set(repos.map((repo) => repo[field]))) + .map((fieldName) => ({ + label: fieldName, + value: fieldName, + })) + .filter((fieldName) => + (fieldName.value as string).toLowerCase().includes(filter.toLowerCase()), + ); + +// Helper function to get the selected option value from a filter and field +const getSelectedOption = ( + filters: Filter, + filterName: keyof Filter, + filterField: string | number, + defaultValue = false, +) => + (filters[filterName] as Record)[filterField] ?? defaultValue; + // Renderer for the min/max filter inputs const MinMaxRenderer: FC<{ headerCellProps: RenderHeaderCellProps; @@ -63,7 +88,7 @@ const MinMaxRenderer: FC<{ return ( {...headerCellProps}> {({ ...rest }) => ( -
+ Min @@ -71,6 +96,7 @@ const MinMaxRenderer: FC<{ { @@ -95,6 +121,7 @@ const MinMaxRenderer: FC<{ @@ -107,7 +134,139 @@ const MinMaxRenderer: FC<{ onClick={(e) => e.stopPropagation()} /> -
+ + )} + + ); +}; + +// Renderer for the searchable select filter +const SearchableSelectRenderer: FC<{ + headerCellProps: RenderHeaderCellProps; + filters: Filter; + updateFilters: ((filters: Filter) => void) & + ((filters: (filters: Filter) => Filter) => void); + filterName: keyof Filter; +}> = ({ headerCellProps, filters, updateFilters, filterName }) => { + const [filteredOptions, setFilteredOptions] = useState(''); + const allSelectOptions = dropdownOptions(filterName, filteredOptions); + + return ( + {...headerCellProps}> + {({ ...rest }) => ( + + setFilteredOptions(e.target.value)} + trailingAction={ + { + setFilteredOptions(''); + }} + icon={XIcon} + aria-label="Clear input" + sx={{ color: 'fg.subtle' }} + /> + } + /> + + + { + updateFilters((otherFilters) => ({ + ...otherFilters, + [filterName]: { + ...otherFilters[filterName], + all: !getSelectedOption(filters, filterName, 'all', true), + }, + })); + }} + > + + + + All + + {allSelectOptions.map((selectOption) => { + if (selectOption.value === '') { + return ( + <> + { + updateFilters((otherFilters) => ({ + ...otherFilters, + [filterName]: { + ...otherFilters[filterName], + [selectOption.value]: !getSelectedOption( + filters, + filterName, + selectOption.value, + ), + }, + })); + }} + > + + + )?.[selectOption.value] ?? false + } + /> + + No License + + + ); + } + + return ( + <> + { + updateFilters((otherFilters) => ({ + ...otherFilters, + [filterName]: { + ...otherFilters[filterName], + [selectOption.value]: !getSelectedOption( + filters, + filterName, + selectOption.value, + ), + }, + })); + }} + > + + + + {selectOption.value} + + + ); + })} + + + )} ); @@ -145,18 +304,20 @@ const HeaderCellRenderer = ({ ref={clickMeButtonRef} // if you'd like a ref to your popover's child, you can grab one here content={() => ( // The click handler here is used to stop the header from being sorted - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -
e.stopPropagation()} + sx={{ + backgroundColor: 'Background', + border: '1px solid', + borderColor: 'border.default', + }} > -
- - Filter by {column.name} - {filterFunction({ tabIndex, filters })} - -
-
+ + Filter by {column.name} + {filterFunction({ tabIndex, filters })} + + )} >