forked from harness/canary
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
first version of filters and sorting
- Loading branch information
1 parent
4a3fcee
commit ba97acd
Showing
7 changed files
with
323 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
packages/ui/src/views/user-management/constants/filter-options.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { type FilterCondition, type FilterOption, type SortDirection, type SortOption } from '@components/filters' | ||
import { TFunction } from 'i18next' | ||
|
||
export const getBasicConditions = (t: TFunction): FilterCondition[] => [ | ||
{ label: t('component:filter.is', 'is'), value: 'is' }, | ||
{ label: t('component:filter.isNot', 'is not'), value: 'is_not' }, | ||
{ label: t('component:filter.isEmpty', 'is empty'), value: 'is_empty' }, | ||
{ label: t('component:filter.isNotEmpty', 'is not empty'), value: 'is_not_empty' } | ||
] | ||
|
||
export const getRangeConditions = (t: TFunction): FilterCondition[] => [ | ||
{ label: t('component:filter.is', 'is'), value: 'is' }, | ||
{ label: t('component:filter.isBefore', 'is before'), value: 'is_before' }, | ||
{ label: t('component:filter.isAfter', 'is after'), value: 'is_after' }, | ||
{ label: t('component:filter.isBetween', 'is between'), value: 'is_between' }, | ||
{ label: t('component:filter.isEmpty', 'is empty'), value: 'is_empty' }, | ||
{ label: t('component:filter.isNotEmpty', 'is not empty'), value: 'is_not_empty' } | ||
] | ||
|
||
export const getTextConditions = (t: TFunction): FilterCondition[] => [ | ||
{ label: t('component:filter.is', 'is'), value: 'is' }, | ||
{ label: t('component:filter.isNot', 'is not'), value: 'is_not' }, | ||
{ label: t('component:filter.contains', 'contains'), value: 'contains' }, | ||
{ label: t('component:filter.doesNotContain', 'does not contain'), value: 'does_not_contain' }, | ||
{ label: t('component:filter.startsWith', 'starts with'), value: 'starts_with' }, | ||
{ label: t('component:filter.endsWith', 'ends with'), value: 'ends_with' }, | ||
{ label: t('component:filter.isEmpty', 'is empty'), value: 'is_empty' }, | ||
{ label: t('component:filter.isNotEmpty', 'is not empty'), value: 'is_not_empty' } | ||
] | ||
|
||
export const getNumberConditions = (t: TFunction): FilterCondition[] => [ | ||
{ label: '=', value: 'equals' }, | ||
{ label: '≠', value: 'not_equals' }, | ||
{ label: '>', value: 'greater' }, | ||
{ label: '<', value: 'less' }, | ||
{ label: '≥', value: 'greater_equals' }, | ||
{ label: '≤', value: 'less_equals' }, | ||
{ label: t('component:filter.isEmpty', 'is empty'), value: 'is_empty' }, | ||
{ label: t('component:filter.isNotEmpty', 'is not empty'), value: 'is_not_empty' } | ||
] | ||
|
||
export const getFilterOptions = (t: TFunction): FilterOption[] => [ | ||
{ | ||
label: t('component:filter.type', 'Type'), | ||
value: 'type', | ||
type: 'checkbox', | ||
conditions: getBasicConditions(t), | ||
options: [ | ||
{ label: t('component:filter.public', 'Public'), value: 'public' }, | ||
{ label: t('component:filter.private', 'Private'), value: 'private' }, | ||
{ label: t('component:filter.fork', 'Fork'), value: 'fork' } | ||
] | ||
}, | ||
{ | ||
label: t('component:filter.createdTime', 'Created Time'), | ||
value: 'created_time', | ||
type: 'calendar', | ||
conditions: getRangeConditions(t) | ||
}, | ||
{ | ||
label: t('component:filter.name', 'Name'), | ||
value: 'name', | ||
type: 'text', | ||
conditions: getTextConditions(t) | ||
}, | ||
{ | ||
label: t('component:filter.stars', 'Stars'), | ||
value: 'stars', | ||
type: 'number', | ||
conditions: getNumberConditions(t) | ||
} | ||
] | ||
|
||
export const getSortOptions = (t: TFunction): SortOption[] => [ | ||
{ label: t('component:sort.lastUpdated', 'Last updated'), value: 'updated' }, | ||
{ label: t('component:sort.stars', 'Stars'), value: 'stars' }, | ||
{ label: t('component:sort.forks', 'Forks'), value: 'forks' }, | ||
{ label: t('component:sort.pullRequests', 'Pull Requests'), value: 'pulls' }, | ||
{ label: t('component:sort.title', 'Title'), value: 'title' } | ||
] | ||
|
||
export const getSortDirections = (t: TFunction): SortDirection[] => [ | ||
{ label: t('component:filter.ascending', 'Ascending'), value: 'asc' }, | ||
{ label: t('component:filter.descending', 'Descending'), value: 'desc' } | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { useFilters } from './use-filters' | ||
|
||
export { useFilters } |
204 changes: 204 additions & 0 deletions
204
packages/ui/src/views/user-management/hooks/use-filters.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
import { useCallback, useState } from 'react' | ||
|
||
import { FilterAction, FilterHandlers, FilterSearchQueries, FilterValue, SortValue } from '@/components/filters/types' | ||
|
||
export const useFilters = (): FilterHandlers => { | ||
// Initialize state with initialState if provided | ||
const [activeFilters, setActiveFilters] = useState<FilterValue[]>([]) | ||
const [activeSorts, setActiveSorts] = useState<SortValue[]>([]) | ||
const [searchQueries, setSearchQueries] = useState<FilterSearchQueries>({ | ||
filters: {}, | ||
menu: {} | ||
}) | ||
const [filterToOpen, setFilterToOpen] = useState<FilterAction | null>(null) | ||
|
||
// Load saved views on mount | ||
|
||
// FILTERS | ||
/** | ||
* Handles adding a new filter to the active filters list | ||
* @param newFilter - Filter object containing: | ||
* - type: string - The type of filter (e.g., 'type', 'language') | ||
* - condition: string - The condition for filtering (will be overridden with default 'is') | ||
* - selectedValues: string[] - Array of selected values (will be initialized as empty) | ||
* | ||
* Only adds the filter if one with the same type doesn't already exist | ||
*/ | ||
const handleFilterChange = ( | ||
newFilter: Omit<FilterValue, 'condition' | 'selectedValues'>, | ||
defaultCondition = 'is' | ||
) => { | ||
setActiveFilters(prevFilters => { | ||
// Indicate which filter should be opened | ||
setFilterToOpen({ type: newFilter.type, kind: 'filter' }) | ||
|
||
if (!prevFilters.find(f => f.type === newFilter.type)) { | ||
return [ | ||
...prevFilters, | ||
{ | ||
...newFilter, | ||
condition: defaultCondition, | ||
selectedValues: [] | ||
} | ||
] | ||
} | ||
return prevFilters | ||
}) | ||
} | ||
|
||
/** | ||
* Updates the selected values for a specific filter type | ||
* @param type - The type of filter to update (e.g., 'type', 'language') | ||
* @param selectedValues - New array of selected values for the filter | ||
* | ||
* Maps through all filters and updates only the matching one | ||
* while preserving other filters unchanged | ||
*/ | ||
const handleUpdateFilter = (type: string, selectedValues: string[]) => { | ||
setActiveFilters(activeFilters.map(filter => (filter.type === type ? { ...filter, selectedValues } : filter))) | ||
} | ||
|
||
/** | ||
* Updates the condition for a specific filter type | ||
* @param type - The type of filter to update (e.g., 'type', 'language') | ||
* @param condition - New condition value (e.g., 'is', 'is_not', 'is_empty') | ||
* | ||
* Special handling for 'is_empty' condition: | ||
* - When 'is_empty' is selected, clears all selected values | ||
* - For other conditions, preserves existing selected values | ||
*/ | ||
const handleUpdateCondition = (type: string, condition: string) => { | ||
setActiveFilters(prevFilters => | ||
prevFilters.map(filter => { | ||
if (filter.type === type) { | ||
return { | ||
...filter, | ||
condition, | ||
selectedValues: condition === 'is_empty' ? [] : filter.selectedValues | ||
} | ||
} | ||
return filter | ||
}) | ||
) | ||
} | ||
|
||
/** | ||
* Removes a filter from the active filters list | ||
* @param type - The type of filter to remove (e.g., 'type', 'language') | ||
* | ||
* Filters out the specified filter type while keeping all others | ||
*/ | ||
const handleRemoveFilter = (type: string) => { | ||
setActiveFilters(prevFilters => prevFilters.filter(filter => filter.type !== type)) | ||
} | ||
|
||
const handleResetFilters = useCallback(() => { | ||
setActiveFilters([]) | ||
}, []) | ||
|
||
// SORTS | ||
/** | ||
* Handles adding a new sort to the active sorts list | ||
* @param newSort - Sort object containing: | ||
* - type: string - The type of sort (e.g., 'updated', 'stars') | ||
* - direction: string - The direction of sort (will be initialized as 'asc') | ||
* | ||
* Only adds the sort if one with the same type doesn't already exist | ||
*/ | ||
const handleSortChange = (newSort: SortValue) => { | ||
// Indicate which filter should be opened | ||
setFilterToOpen({ type: newSort.type, kind: 'sort' }) | ||
|
||
if (!activeSorts.find(sort => sort.type === newSort.type)) { | ||
setActiveSorts([...activeSorts, newSort]) | ||
} | ||
} | ||
|
||
/** | ||
* Updates a specific sort in the active sorts list | ||
* @param index - The index of the sort to update | ||
* @param updatedSort - The new sort object to replace the existing one | ||
* | ||
* Maps through all sorts and updates only the matching one | ||
* while preserving other sorts unchanged | ||
*/ | ||
const handleUpdateSort = (index: number, updatedSort: SortValue) => { | ||
setActiveSorts(activeSorts.map((sort, i) => (i === index ? updatedSort : sort))) | ||
} | ||
|
||
/** | ||
* Removes a sort from the active sorts list | ||
* @param index - The index of the sort to remove | ||
* | ||
* Filters out the specified sort index while keeping all others | ||
*/ | ||
const handleRemoveSort = (index: number) => { | ||
setActiveSorts(activeSorts.filter((_, i) => i !== index)) | ||
} | ||
|
||
const handleReorderSorts = (newSorts: SortValue[]) => { | ||
setActiveSorts(newSorts) | ||
} | ||
|
||
const handleResetSorts = () => { | ||
setActiveSorts([]) | ||
} | ||
|
||
const handleResetAll = useCallback(() => { | ||
setActiveFilters([]) | ||
setActiveSorts([]) | ||
setSearchQueries({ filters: {}, menu: {} }) | ||
}, []) | ||
|
||
// SEARCH | ||
const handleSearchChange = (type: string, query: string, searchType: keyof FilterSearchQueries) => { | ||
setSearchQueries(prev => ({ | ||
...prev, | ||
[searchType]: { | ||
...prev[searchType], | ||
[type]: query | ||
} | ||
})) | ||
} | ||
|
||
const clearSearchQuery = (type: string, searchType: keyof FilterSearchQueries) => { | ||
setSearchQueries(prev => ({ | ||
...prev, | ||
[searchType]: { | ||
...prev[searchType], | ||
[type]: '' | ||
} | ||
})) | ||
} | ||
|
||
// Clear the filter to open | ||
const clearFilterToOpen = () => { | ||
setFilterToOpen(null) | ||
} | ||
|
||
return { | ||
// Filters | ||
activeFilters, | ||
activeSorts, | ||
setActiveFilters, | ||
setActiveSorts, | ||
searchQueries, | ||
filterToOpen, | ||
handleFilterChange, | ||
handleUpdateFilter, | ||
handleUpdateCondition, | ||
handleRemoveFilter, | ||
handleResetFilters, | ||
clearFilterToOpen, | ||
// Sorts | ||
handleSortChange, | ||
handleUpdateSort, | ||
handleRemoveSort, | ||
handleReorderSorts, | ||
handleResetSorts, | ||
handleResetAll, | ||
// Search | ||
handleSearchChange, | ||
clearSearchQuery | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters