mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: Add state filter to UI query; default to open (#10858)
Add an open/closed filter to the global change requests table. To accomodate this, the PR: - refactors the previous `ChangeRequestFilters` into its own directory with additional files for each of the filter groups. - updates the change request filters to work based on the table state instead of its own external state (this was fine with only one param, but would've gotten too complicated with two). <img width="1108" height="442" alt="image" src="https://github.com/user-attachments/assets/9cda0cbc-8524-45ce-b201-260e9581a346" /> <img width="1101" height="381" alt="image" src="https://github.com/user-attachments/assets/ab77b17f-5449-4987-9d08-341e856cb7ac" />
This commit is contained in:
		
							parent
							
								
									fd4fa815a9
								
							
						
					
					
						commit
						1932d21336
					
				@ -1,105 +0,0 @@
 | 
				
			|||||||
import { Box, Chip, styled, type ChipProps } from '@mui/material';
 | 
					 | 
				
			||||||
import { useState, type FC } from 'react';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const makeStyledChip = (ariaControlTarget: string) =>
 | 
					 | 
				
			||||||
    styled(({ ...props }: ChipProps) => (
 | 
					 | 
				
			||||||
        <Chip variant='outlined' aria-controls={ariaControlTarget} {...props} />
 | 
					 | 
				
			||||||
    ))<ChipProps>(({ theme, label }) => ({
 | 
					 | 
				
			||||||
        padding: theme.spacing(0.5),
 | 
					 | 
				
			||||||
        fontSize: theme.typography.body2.fontSize,
 | 
					 | 
				
			||||||
        height: 'auto',
 | 
					 | 
				
			||||||
        '&[aria-current="true"]': {
 | 
					 | 
				
			||||||
            backgroundColor: theme.palette.secondary.light,
 | 
					 | 
				
			||||||
            fontWeight: 'bold',
 | 
					 | 
				
			||||||
            borderColor: theme.palette.primary.main,
 | 
					 | 
				
			||||||
            color: theme.palette.primary.main,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        ':focus-visible': {
 | 
					 | 
				
			||||||
            outline: `1px solid ${theme.palette.primary.main}`,
 | 
					 | 
				
			||||||
            borderColor: theme.palette.primary.main,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        borderRadius: 0,
 | 
					 | 
				
			||||||
        '&:first-of-type': {
 | 
					 | 
				
			||||||
            borderTopLeftRadius: theme.shape.borderRadius,
 | 
					 | 
				
			||||||
            borderBottomLeftRadius: theme.shape.borderRadius,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        '&:last-of-type': {
 | 
					 | 
				
			||||||
            borderTopRightRadius: theme.shape.borderRadius,
 | 
					 | 
				
			||||||
            borderBottomRightRadius: theme.shape.borderRadius,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        '& .MuiChip-label': {
 | 
					 | 
				
			||||||
            position: 'relative',
 | 
					 | 
				
			||||||
            textAlign: 'center',
 | 
					 | 
				
			||||||
            '&::before': {
 | 
					 | 
				
			||||||
                content: `'${label}'`,
 | 
					 | 
				
			||||||
                fontWeight: 'bold',
 | 
					 | 
				
			||||||
                visibility: 'hidden',
 | 
					 | 
				
			||||||
                height: 0,
 | 
					 | 
				
			||||||
                display: 'block',
 | 
					 | 
				
			||||||
                overflow: 'hidden',
 | 
					 | 
				
			||||||
                userSelect: 'none',
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type ChangeRequestQuickFilter = 'Created' | 'Approval Requested';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface IChangeRequestFiltersProps {
 | 
					 | 
				
			||||||
    ariaControlTarget: string;
 | 
					 | 
				
			||||||
    initialSelection?: ChangeRequestQuickFilter;
 | 
					 | 
				
			||||||
    onSelectionChange: (selection: ChangeRequestQuickFilter) => void;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Wrapper = styled(Box)(({ theme }) => ({
 | 
					 | 
				
			||||||
    display: 'flex',
 | 
					 | 
				
			||||||
    justifyContent: 'flex-start',
 | 
					 | 
				
			||||||
    padding: theme.spacing(1.5, 3, 0, 3),
 | 
					 | 
				
			||||||
    minHeight: theme.spacing(7),
 | 
					 | 
				
			||||||
    gap: theme.spacing(2),
 | 
					 | 
				
			||||||
}));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const StyledContainer = styled(Box)({
 | 
					 | 
				
			||||||
    display: 'flex',
 | 
					 | 
				
			||||||
    alignItems: 'center',
 | 
					 | 
				
			||||||
    flexWrap: 'wrap',
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const ChangeRequestFilters: FC<IChangeRequestFiltersProps> = ({
 | 
					 | 
				
			||||||
    onSelectionChange,
 | 
					 | 
				
			||||||
    initialSelection,
 | 
					 | 
				
			||||||
    ariaControlTarget,
 | 
					 | 
				
			||||||
}) => {
 | 
					 | 
				
			||||||
    const [selected, setSelected] = useState<ChangeRequestQuickFilter>(
 | 
					 | 
				
			||||||
        initialSelection || 'Created',
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    const handleSelectionChange = (value: ChangeRequestQuickFilter) => () => {
 | 
					 | 
				
			||||||
        if (value === selected) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        setSelected(value);
 | 
					 | 
				
			||||||
        onSelectionChange(value);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const StyledChip = makeStyledChip(ariaControlTarget);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <Wrapper>
 | 
					 | 
				
			||||||
            <StyledContainer>
 | 
					 | 
				
			||||||
                <StyledChip
 | 
					 | 
				
			||||||
                    label={'Created'}
 | 
					 | 
				
			||||||
                    aria-current={selected === 'Created'}
 | 
					 | 
				
			||||||
                    onClick={handleSelectionChange('Created')}
 | 
					 | 
				
			||||||
                    title={'Show change requests created by you'}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                <StyledChip
 | 
					 | 
				
			||||||
                    label={'Approval Requested'}
 | 
					 | 
				
			||||||
                    aria-current={selected === 'Approval Requested'}
 | 
					 | 
				
			||||||
                    onClick={handleSelectionChange('Approval Requested')}
 | 
					 | 
				
			||||||
                    title={'Show change requests requesting your approval'}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
            </StyledContainer>
 | 
					 | 
				
			||||||
        </Wrapper>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					import { Box, Chip, styled, type ChipProps } from '@mui/material';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const makeStyledChip = (ariaControlTarget: string) =>
 | 
				
			||||||
 | 
					    styled(({ ...props }: ChipProps) => (
 | 
				
			||||||
 | 
					        <Chip variant='outlined' aria-controls={ariaControlTarget} {...props} />
 | 
				
			||||||
 | 
					    ))<ChipProps>(({ theme, label }) => ({
 | 
				
			||||||
 | 
					        padding: theme.spacing(0.5),
 | 
				
			||||||
 | 
					        fontSize: theme.typography.body2.fontSize,
 | 
				
			||||||
 | 
					        height: 'auto',
 | 
				
			||||||
 | 
					        '&[aria-current="true"]': {
 | 
				
			||||||
 | 
					            backgroundColor: theme.palette.secondary.light,
 | 
				
			||||||
 | 
					            fontWeight: 'bold',
 | 
				
			||||||
 | 
					            borderColor: theme.palette.primary.main,
 | 
				
			||||||
 | 
					            color: theme.palette.primary.main,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        ':focus-visible': {
 | 
				
			||||||
 | 
					            outline: `1px solid ${theme.palette.primary.main}`,
 | 
				
			||||||
 | 
					            borderColor: theme.palette.primary.main,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        borderRadius: 0,
 | 
				
			||||||
 | 
					        '&:first-of-type': {
 | 
				
			||||||
 | 
					            borderTopLeftRadius: theme.shape.borderRadius,
 | 
				
			||||||
 | 
					            borderBottomLeftRadius: theme.shape.borderRadius,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        '&:last-of-type': {
 | 
				
			||||||
 | 
					            borderTopRightRadius: theme.shape.borderRadius,
 | 
				
			||||||
 | 
					            borderBottomRightRadius: theme.shape.borderRadius,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '&:not(&[aria-current="true"])': {
 | 
				
			||||||
 | 
					            '&:not(&:first-of-type)': {
 | 
				
			||||||
 | 
					                borderLeftWidth: 0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            '&:not(&:last-of-type)': {
 | 
				
			||||||
 | 
					                borderRightWidth: 0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '& .MuiChip-label': {
 | 
				
			||||||
 | 
					            position: 'relative',
 | 
				
			||||||
 | 
					            textAlign: 'center',
 | 
				
			||||||
 | 
					            '&::before': {
 | 
				
			||||||
 | 
					                content: `'${label}'`,
 | 
				
			||||||
 | 
					                fontWeight: 'bold',
 | 
				
			||||||
 | 
					                visibility: 'hidden',
 | 
				
			||||||
 | 
					                height: 0,
 | 
				
			||||||
 | 
					                display: 'block',
 | 
				
			||||||
 | 
					                overflow: 'hidden',
 | 
				
			||||||
 | 
					                userSelect: 'none',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Wrapper = styled(Box)(({ theme }) => ({
 | 
				
			||||||
 | 
					    display: 'flex',
 | 
				
			||||||
 | 
					    justifyContent: 'flex-start',
 | 
				
			||||||
 | 
					    flexFlow: 'row wrap',
 | 
				
			||||||
 | 
					    padding: theme.spacing(1.5, 3, 0, 3),
 | 
				
			||||||
 | 
					    minHeight: theme.spacing(7),
 | 
				
			||||||
 | 
					    gap: theme.spacing(2),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const StyledContainer = styled(Box)({
 | 
				
			||||||
 | 
					    display: 'flex',
 | 
				
			||||||
 | 
					    alignItems: 'center',
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import type { FC } from 'react';
 | 
				
			||||||
 | 
					import type { TableState } from '../ChangeRequests.types';
 | 
				
			||||||
 | 
					import { makeStyledChip, Wrapper } from './ChangeRequestFilters.styles';
 | 
				
			||||||
 | 
					import { UserFilterChips } from './UserFilterChips.tsx';
 | 
				
			||||||
 | 
					import { StateFilterChips } from './StateFilterChips.tsx';
 | 
				
			||||||
 | 
					import type { ChangeRequestFiltersProps } from './ChangeRequestFilters.types.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ChangeRequestFilters: FC<ChangeRequestFiltersProps> = ({
 | 
				
			||||||
 | 
					    tableState,
 | 
				
			||||||
 | 
					    setTableState,
 | 
				
			||||||
 | 
					    userId,
 | 
				
			||||||
 | 
					    ariaControlTarget,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					    const updateTableState = (update: Partial<TableState>) => {
 | 
				
			||||||
 | 
					        setTableState({ ...tableState, ...update, offset: 0 });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const StyledChip = makeStyledChip(ariaControlTarget);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Wrapper>
 | 
				
			||||||
 | 
					            <UserFilterChips
 | 
				
			||||||
 | 
					                tableState={tableState}
 | 
				
			||||||
 | 
					                setTableState={updateTableState}
 | 
				
			||||||
 | 
					                userId={userId}
 | 
				
			||||||
 | 
					                StyledChip={StyledChip}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <StateFilterChips
 | 
				
			||||||
 | 
					                tableState={tableState}
 | 
				
			||||||
 | 
					                setTableState={updateTableState}
 | 
				
			||||||
 | 
					                StyledChip={StyledChip}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </Wrapper>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import type { TableState } from '../ChangeRequests.types';
 | 
				
			||||||
 | 
					import type { makeStyledChip } from './ChangeRequestFilters.styles';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ChangeRequestFiltersProps = {
 | 
				
			||||||
 | 
					    tableState: Readonly<TableState>;
 | 
				
			||||||
 | 
					    setTableState: (update: Partial<TableState>) => void;
 | 
				
			||||||
 | 
					    userId: number;
 | 
				
			||||||
 | 
					    ariaControlTarget: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type FilterChipsProps = {
 | 
				
			||||||
 | 
					    tableState: ChangeRequestFiltersProps['tableState'];
 | 
				
			||||||
 | 
					    setTableState: ChangeRequestFiltersProps['setTableState'];
 | 
				
			||||||
 | 
					    StyledChip: ReturnType<typeof makeStyledChip>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					import type { FC } from 'react';
 | 
				
			||||||
 | 
					import { StyledContainer } from './ChangeRequestFilters.styles';
 | 
				
			||||||
 | 
					import type { FilterChipsProps } from './ChangeRequestFilters.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type StateFilterType = 'open' | 'closed';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getStateFilter = (
 | 
				
			||||||
 | 
					    stateValue: string | undefined,
 | 
				
			||||||
 | 
					): StateFilterType | undefined => {
 | 
				
			||||||
 | 
					    if (stateValue === 'open') {
 | 
				
			||||||
 | 
					        return 'open';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (stateValue === 'closed') {
 | 
				
			||||||
 | 
					        return 'closed';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const StateFilterChips: FC<FilterChipsProps> = ({
 | 
				
			||||||
 | 
					    tableState,
 | 
				
			||||||
 | 
					    setTableState,
 | 
				
			||||||
 | 
					    StyledChip,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					    const activeStateFilter = getStateFilter(tableState.state?.values?.[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleStateFilterChange = (filter: StateFilterType) => () => {
 | 
				
			||||||
 | 
					        if (filter === activeStateFilter) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        setTableState({ state: { operator: 'IS' as const, values: [filter] } });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <StyledContainer>
 | 
				
			||||||
 | 
					            <StyledChip
 | 
				
			||||||
 | 
					                label={'Open'}
 | 
				
			||||||
 | 
					                aria-current={activeStateFilter === 'open'}
 | 
				
			||||||
 | 
					                onClick={handleStateFilterChange('open')}
 | 
				
			||||||
 | 
					                title={'Show open change requests'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <StyledChip
 | 
				
			||||||
 | 
					                label={'Closed'}
 | 
				
			||||||
 | 
					                aria-current={activeStateFilter === 'closed'}
 | 
				
			||||||
 | 
					                onClick={handleStateFilterChange('closed')}
 | 
				
			||||||
 | 
					                title={'Show closed change requests'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </StyledContainer>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import type { FC } from 'react';
 | 
				
			||||||
 | 
					import type { TableState } from '../ChangeRequests.types';
 | 
				
			||||||
 | 
					import { StyledContainer } from './ChangeRequestFilters.styles';
 | 
				
			||||||
 | 
					import type { FilterChipsProps } from './ChangeRequestFilters.types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserFilterType = 'created' | 'approval requested';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UserFilterChipsProps = FilterChipsProps & { userId: number };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getUserFilter = (
 | 
				
			||||||
 | 
					    tableState: TableState,
 | 
				
			||||||
 | 
					    userId: string,
 | 
				
			||||||
 | 
					): UserFilterType | undefined => {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        !tableState.requestedApproverId &&
 | 
				
			||||||
 | 
					        tableState.createdBy?.values.length === 1 &&
 | 
				
			||||||
 | 
					        tableState.createdBy.values[0] === userId
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        return 'created';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        !tableState.createdBy &&
 | 
				
			||||||
 | 
					        tableState.requestedApproverId?.values.length === 1 &&
 | 
				
			||||||
 | 
					        tableState.requestedApproverId.values[0] === userId
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        return 'approval requested';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const UserFilterChips: FC<UserFilterChipsProps> = ({
 | 
				
			||||||
 | 
					    tableState,
 | 
				
			||||||
 | 
					    setTableState,
 | 
				
			||||||
 | 
					    userId,
 | 
				
			||||||
 | 
					    StyledChip,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					    const userIdString = userId.toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const activeUserFilter: UserFilterType | undefined = getUserFilter(
 | 
				
			||||||
 | 
					        tableState,
 | 
				
			||||||
 | 
					        userIdString,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const handleUserFilterChange = (filter: UserFilterType) => () => {
 | 
				
			||||||
 | 
					        if (filter === activeUserFilter) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const [targetProperty, otherProperty] =
 | 
				
			||||||
 | 
					            filter === 'created'
 | 
				
			||||||
 | 
					                ? (['createdBy', 'requestedApproverId'] as const)
 | 
				
			||||||
 | 
					                : (['requestedApproverId', 'createdBy'] as const);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setTableState({
 | 
				
			||||||
 | 
					            [targetProperty]: {
 | 
				
			||||||
 | 
					                operator: 'IS' as const,
 | 
				
			||||||
 | 
					                values: [userIdString],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            [otherProperty]:
 | 
				
			||||||
 | 
					                tableState[otherProperty]?.values.length === 1 &&
 | 
				
			||||||
 | 
					                tableState[otherProperty]?.values[0] === userIdString
 | 
				
			||||||
 | 
					                    ? null
 | 
				
			||||||
 | 
					                    : tableState[otherProperty],
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <StyledContainer>
 | 
				
			||||||
 | 
					            <StyledChip
 | 
				
			||||||
 | 
					                label={'Created'}
 | 
				
			||||||
 | 
					                aria-current={activeUserFilter === 'created'}
 | 
				
			||||||
 | 
					                onClick={handleUserFilterChange('created')}
 | 
				
			||||||
 | 
					                title={'Show change requests created by you'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            <StyledChip
 | 
				
			||||||
 | 
					                label={'Approval Requested'}
 | 
				
			||||||
 | 
					                aria-current={activeUserFilter === 'approval requested'}
 | 
				
			||||||
 | 
					                onClick={handleUserFilterChange('approval requested')}
 | 
				
			||||||
 | 
					                title={'Show change requests requesting your approval'}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        </StyledContainer>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -14,25 +14,16 @@ import { useUiFlag } from 'hooks/useUiFlag.js';
 | 
				
			|||||||
import { withTableState } from 'utils/withTableState';
 | 
					import { withTableState } from 'utils/withTableState';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    useChangeRequestSearch,
 | 
					    useChangeRequestSearch,
 | 
				
			||||||
    DEFAULT_PAGE_LIMIT,
 | 
					 | 
				
			||||||
    type SearchChangeRequestsInput,
 | 
					    type SearchChangeRequestsInput,
 | 
				
			||||||
} from 'hooks/api/getters/useChangeRequestSearch/useChangeRequestSearch';
 | 
					} from 'hooks/api/getters/useChangeRequestSearch/useChangeRequestSearch';
 | 
				
			||||||
import type { ChangeRequestSearchItemSchema } from 'openapi';
 | 
					import type { ChangeRequestSearchItemSchema } from 'openapi';
 | 
				
			||||||
import {
 | 
					import { useQueryParams, encodeQueryParams } from 'use-query-params';
 | 
				
			||||||
    NumberParam,
 | 
					 | 
				
			||||||
    StringParam,
 | 
					 | 
				
			||||||
    withDefault,
 | 
					 | 
				
			||||||
    useQueryParams,
 | 
					 | 
				
			||||||
    encodeQueryParams,
 | 
					 | 
				
			||||||
} from 'use-query-params';
 | 
					 | 
				
			||||||
import useLoading from 'hooks/useLoading';
 | 
					import useLoading from 'hooks/useLoading';
 | 
				
			||||||
import { styles as themeStyles } from 'component/common';
 | 
					import { styles as themeStyles } from 'component/common';
 | 
				
			||||||
import { FilterItemParam } from 'utils/serializeQueryParams';
 | 
					import { ChangeRequestFilters } from './ChangeRequestFilters/ChangeRequestFilters.js';
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
    ChangeRequestFilters,
 | 
					 | 
				
			||||||
    type ChangeRequestQuickFilter,
 | 
					 | 
				
			||||||
} from './ChangeRequestFilters.js';
 | 
					 | 
				
			||||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser.js';
 | 
					import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser.js';
 | 
				
			||||||
 | 
					import type { IUser } from 'interfaces/user.js';
 | 
				
			||||||
 | 
					import { stateConfig } from './ChangeRequests.types.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const columnHelper = createColumnHelper<ChangeRequestSearchItemSchema>();
 | 
					const columnHelper = createColumnHelper<ChangeRequestSearchItemSchema>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -49,46 +40,33 @@ const StyledPaginatedTable = styled(
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ChangeRequestsInner = () => {
 | 
					const defaultTableState = (user: IUser) => ({
 | 
				
			||||||
    const { user } = useAuthUser();
 | 
					    createdBy: {
 | 
				
			||||||
 | 
					        operator: 'IS' as const,
 | 
				
			||||||
 | 
					        values: [user.id.toString()],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    state: {
 | 
				
			||||||
 | 
					        operator: 'IS' as const,
 | 
				
			||||||
 | 
					        values: ['open'],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ChangeRequestsInner = ({ user }: { user: IUser }) => {
 | 
				
			||||||
    const urlParams = new URLSearchParams(window.location.search);
 | 
					    const urlParams = new URLSearchParams(window.location.search);
 | 
				
			||||||
    const shouldApplyDefaults =
 | 
					    const shouldApplyDefaults = !urlParams.toString();
 | 
				
			||||||
        user &&
 | 
					 | 
				
			||||||
        !urlParams.has('createdBy') &&
 | 
					 | 
				
			||||||
        !urlParams.has('requestedApproverId');
 | 
					 | 
				
			||||||
    const initialFilter = urlParams.has('requestedApproverId')
 | 
					 | 
				
			||||||
        ? 'Approval Requested'
 | 
					 | 
				
			||||||
        : 'Created';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const stateConfig = {
 | 
					    const initialState = shouldApplyDefaults ? defaultTableState(user) : {};
 | 
				
			||||||
        offset: withDefault(NumberParam, 0),
 | 
					 | 
				
			||||||
        limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
 | 
					 | 
				
			||||||
        sortBy: withDefault(StringParam, 'createdAt'),
 | 
					 | 
				
			||||||
        sortOrder: withDefault(StringParam, 'desc'),
 | 
					 | 
				
			||||||
        createdBy: FilterItemParam,
 | 
					 | 
				
			||||||
        requestedApproverId: FilterItemParam,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const initialState = shouldApplyDefaults
 | 
					 | 
				
			||||||
        ? {
 | 
					 | 
				
			||||||
              createdBy: {
 | 
					 | 
				
			||||||
                  operator: 'IS' as const,
 | 
					 | 
				
			||||||
                  values: [user.id.toString()],
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        : {};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const [tableState, setTableState] = useQueryParams(stateConfig, {
 | 
					    const [tableState, setTableState] = useQueryParams(stateConfig, {
 | 
				
			||||||
        updateType: 'replaceIn',
 | 
					        updateType: 'replaceIn',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const effectiveTableState = useMemo(
 | 
					    const effectiveTableState = shouldApplyDefaults
 | 
				
			||||||
        () => ({
 | 
					        ? {
 | 
				
			||||||
            ...tableState,
 | 
					              ...tableState,
 | 
				
			||||||
            ...initialState,
 | 
					              ...initialState,
 | 
				
			||||||
        }),
 | 
					          }
 | 
				
			||||||
        [initialState, tableState],
 | 
					        : tableState;
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
        changeRequests: data,
 | 
					        changeRequests: data,
 | 
				
			||||||
@ -172,30 +150,6 @@ const ChangeRequestsInner = () => {
 | 
				
			|||||||
        }),
 | 
					        }),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const tableId = useId();
 | 
					    const tableId = useId();
 | 
				
			||||||
    const handleQuickFilterChange = (filter: ChangeRequestQuickFilter) => {
 | 
					 | 
				
			||||||
        if (!user) {
 | 
					 | 
				
			||||||
            // todo (globalChangeRequestList): handle this somehow? Or just ignore.
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const [targetProperty, otherProperty] =
 | 
					 | 
				
			||||||
            filter === 'Created'
 | 
					 | 
				
			||||||
                ? ['createdBy', 'requestedApproverId']
 | 
					 | 
				
			||||||
                : ['requestedApproverId', 'createdBy'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // todo (globalChangeRequestList): extract and test the logic for wiping out createdby/requestedapproverid
 | 
					 | 
				
			||||||
        setTableState((state) => ({
 | 
					 | 
				
			||||||
            [targetProperty]: {
 | 
					 | 
				
			||||||
                operator: 'IS',
 | 
					 | 
				
			||||||
                values: [user.id.toString()],
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            [otherProperty]:
 | 
					 | 
				
			||||||
                state[otherProperty]?.values.length === 1 &&
 | 
					 | 
				
			||||||
                state[otherProperty].values[0] === user.id.toString()
 | 
					 | 
				
			||||||
                    ? null
 | 
					 | 
				
			||||||
                    : state[otherProperty],
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const bodyLoadingRef = useLoading(loading);
 | 
					    const bodyLoadingRef = useLoading(loading);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
@ -205,8 +159,9 @@ const ChangeRequestsInner = () => {
 | 
				
			|||||||
        >
 | 
					        >
 | 
				
			||||||
            <ChangeRequestFilters
 | 
					            <ChangeRequestFilters
 | 
				
			||||||
                ariaControlTarget={tableId}
 | 
					                ariaControlTarget={tableId}
 | 
				
			||||||
                initialSelection={initialFilter}
 | 
					                tableState={effectiveTableState}
 | 
				
			||||||
                onSelectionChange={handleQuickFilterChange}
 | 
					                setTableState={setTableState}
 | 
				
			||||||
 | 
					                userId={user.id}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <div
 | 
					            <div
 | 
				
			||||||
@ -231,6 +186,7 @@ const ChangeRequestsInner = () => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const ChangeRequests = () => {
 | 
					export const ChangeRequests = () => {
 | 
				
			||||||
 | 
					    const { user } = useAuthUser();
 | 
				
			||||||
    if (!useUiFlag('globalChangeRequestList')) {
 | 
					    if (!useUiFlag('globalChangeRequestList')) {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <PageContent header={<PageHeader title='Change requests' />}>
 | 
					            <PageContent header={<PageHeader title='Change requests' />}>
 | 
				
			||||||
@ -239,5 +195,16 @@ export const ChangeRequests = () => {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <ChangeRequestsInner />;
 | 
					    if (!user) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <PageContent header={<PageHeader title='Change requests' />}>
 | 
				
			||||||
 | 
					                <p>
 | 
				
			||||||
 | 
					                    Failed to get your user information. Please refresh. If the
 | 
				
			||||||
 | 
					                    problem persists, get in touch.
 | 
				
			||||||
 | 
					                </p>
 | 
				
			||||||
 | 
					            </PageContent>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return <ChangeRequestsInner user={user} />;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					import { DEFAULT_PAGE_LIMIT } from 'hooks/api/getters/useChangeRequestSearch/useChangeRequestSearch';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    NumberParam,
 | 
				
			||||||
 | 
					    StringParam,
 | 
				
			||||||
 | 
					    withDefault,
 | 
				
			||||||
 | 
					    type DecodedValueMap,
 | 
				
			||||||
 | 
					} from 'use-query-params';
 | 
				
			||||||
 | 
					import { FilterItemParam } from 'utils/serializeQueryParams';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const stateConfig = {
 | 
				
			||||||
 | 
					    offset: withDefault(NumberParam, 0),
 | 
				
			||||||
 | 
					    limit: withDefault(NumberParam, DEFAULT_PAGE_LIMIT),
 | 
				
			||||||
 | 
					    sortBy: withDefault(StringParam, 'createdAt'),
 | 
				
			||||||
 | 
					    sortOrder: withDefault(StringParam, 'desc'),
 | 
				
			||||||
 | 
					    createdBy: FilterItemParam,
 | 
				
			||||||
 | 
					    requestedApproverId: FilterItemParam,
 | 
				
			||||||
 | 
					    state: FilterItemParam,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TableState = DecodedValueMap<typeof stateConfig>;
 | 
				
			||||||
@ -1303,6 +1303,7 @@ export * from './searchChangeRequests401.js';
 | 
				
			|||||||
export * from './searchChangeRequests403.js';
 | 
					export * from './searchChangeRequests403.js';
 | 
				
			||||||
export * from './searchChangeRequests404.js';
 | 
					export * from './searchChangeRequests404.js';
 | 
				
			||||||
export * from './searchChangeRequestsParams.js';
 | 
					export * from './searchChangeRequestsParams.js';
 | 
				
			||||||
 | 
					export * from './searchChangeRequestsState.js';
 | 
				
			||||||
export * from './searchEventsParams.js';
 | 
					export * from './searchEventsParams.js';
 | 
				
			||||||
export * from './searchFeatures401.js';
 | 
					export * from './searchFeatures401.js';
 | 
				
			||||||
export * from './searchFeatures403.js';
 | 
					export * from './searchFeatures403.js';
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user