import { useCallback, useMemo } from 'react'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { PageContent } from 'component/common/PageContent/PageContent'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { DateCell } from 'component/common/Table/cells/DateCell/DateCell'; import { PaginatedTable } from 'component/common/Table'; import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; import { FavoriteIconHeader } from 'component/common/Table/FavoriteIconHeader/FavoriteIconHeader'; import { FavoriteIconCell } from 'component/common/Table/cells/FavoriteIconCell/FavoriteIconCell'; import { ActionsCell } from '../ProjectFeatureToggles/ActionsCell/ActionsCell'; import { ExperimentalColumnsMenu as ColumnsMenu } from './ExperimentalColumnsMenu/ExperimentalColumnsMenu'; import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi'; import { MemoizedRowSelectCell } from '../ProjectFeatureToggles/RowSelectCell/RowSelectCell'; import { BatchSelectionActionsBar } from 'component/common/BatchSelectionActionsBar/BatchSelectionActionsBar'; import { ProjectFeaturesBatchActions } from '../ProjectFeatureToggles/ProjectFeaturesBatchActions/ProjectFeaturesBatchActions'; import { FeatureLifecycleCell, MemoizedFeatureEnvironmentSeenCell, } from 'component/common/Table/cells/FeatureSeenCell/FeatureEnvironmentSeenCell'; import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; import { useFeatureToggleSwitch } from '../ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; import useLoading from 'hooks/useLoading'; import { ProjectFeatureTogglesHeader } from './ProjectFeatureTogglesHeader/ProjectFeatureTogglesHeader'; import { createColumnHelper, useReactTable } from '@tanstack/react-table'; import { withTableState } from 'utils/withTableState'; import type { FeatureSearchResponseSchema } from 'openapi'; import { FeatureToggleCell, PlaceholderFeatureToggleCell, } from './FeatureToggleCell/FeatureToggleCell'; import { ProjectOverviewFilters } from './ProjectOverviewFilters'; import { useDefaultColumnVisibility } from './hooks/useDefaultColumnVisibility'; import { TableEmptyState } from './TableEmptyState/TableEmptyState'; import { useRowActions } from './hooks/useRowActions'; import { useSelectedData } from './hooks/useSelectedData'; import { FeatureOverviewCell } from '../../../common/Table/cells/FeatureOverviewCell/FeatureOverviewCell'; import { useProjectFeatureSearch, useProjectFeatureSearchActions, } from './useProjectFeatureSearch'; import { AvatarCell } from './AvatarCell'; import { ProjectOnboarding } from './ProjectOnboarding/ProjectOnboarding'; import { useUiFlag } from 'hooks/useUiFlag'; import { styled } from '@mui/material'; import { ConnectSDKDialog } from '../../../onboarding/ConnectSDKDialog'; interface IPaginatedProjectFeatureTogglesProps { environments: string[]; } const formatEnvironmentColumnId = (environment: string) => `environment:${environment}`; const columnHelper = createColumnHelper(); const getRowId = (row: { name: string }) => row.name; const Container = styled('div')(({ theme }) => ({ display: 'flex', flexDirection: 'column', gap: theme.spacing(2), })); export const ProjectFeatureToggles = ({ environments, }: IPaginatedProjectFeatureTogglesProps) => { const onboardingUIEnabled = useUiFlag('onboardingUI'); const projectId = useRequiredPathParam('projectId'); const { features, total, refetch, loading, initialLoad, tableState, setTableState, } = useProjectFeatureSearch(projectId); const { onFlagTypeClick, onTagClick, onAvatarClick } = useProjectFeatureSearchActions(tableState, setTableState); const filterState = { tag: tableState.tag, createdAt: tableState.createdAt, type: tableState.type, state: tableState.state, createdBy: tableState.createdBy, }; const { favorite, unfavorite } = useFavoriteFeaturesApi(); const onFavorite = useCallback( async (feature: FeatureSearchResponseSchema) => { if (feature?.favorite) { await unfavorite(projectId, feature.name); } else { await favorite(projectId, feature.name); } refetch(); }, [projectId, refetch], ); const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); const { onToggle: onFeatureToggle, modals: featureToggleModals } = useFeatureToggleSwitch(projectId); const { rowActionsDialogs, setFeatureArchiveState, setFeatureStaleDialogState, setShowMarkCompletedDialogue, } = useRowActions(refetch, projectId); const isPlaceholder = Boolean(initialLoad || (loading && total)); const columns = useMemo( () => [ columnHelper.display({ id: 'select', header: ({ table }) => ( ), cell: ({ row }) => ( ), meta: { width: '1%', }, enableHiding: false, }), columnHelper.accessor('favorite', { id: 'favorite', header: () => ( setTableState({ favoritesFirst: !tableState.favoritesFirst, }) } /> ), cell: ({ row: { original: feature } }) => ( onFavorite(feature)} /> ), enableSorting: false, enableHiding: false, meta: { align: 'center', width: '1%', }, }), columnHelper.accessor('name', { id: 'name', header: 'Name', cell: FeatureOverviewCell(onTagClick, onFlagTypeClick), enableHiding: false, meta: { width: '50%', }, }), columnHelper.accessor('createdAt', { id: 'createdAt', header: 'Created', cell: DateCell, meta: { width: '1%', }, }), columnHelper.accessor('createdBy', { id: 'createdBy', header: 'By', cell: AvatarCell(onAvatarClick), enableSorting: false, meta: { width: '1%', align: 'center', }, }), columnHelper.accessor('lastSeenAt', { id: 'lastSeenAt', header: 'Last seen', cell: ({ row: { original } }) => ( ), size: 50, meta: { align: 'center', width: '1%', }, }), columnHelper.accessor('lifecycle', { id: 'lifecycle', header: 'Lifecycle', cell: ({ row: { original } }) => ( { setShowMarkCompletedDialogue({ featureId: original.name, open: true, }); }} onUncomplete={refetch} onArchive={() => setFeatureArchiveState(original.name)} data-loading /> ), enableSorting: false, size: 50, meta: { align: 'center', width: '1%', }, }), ...environments.map((name: string) => { const isChangeRequestEnabled = isChangeRequestConfigured(name); return columnHelper.accessor( (row) => ({ featureId: row.name, environment: row.environments?.find( (featureEnvironment) => featureEnvironment.name === name, ), someEnabledEnvironmentHasVariants: row.environments?.some( (featureEnvironment) => featureEnvironment.variantCount && featureEnvironment.variantCount > 0 && featureEnvironment.enabled, ) || false, }), { id: formatEnvironmentColumnId(name), header: name, meta: { align: 'center', width: 90, }, cell: ({ getValue }) => { const { featureId, environment, someEnabledEnvironmentHasVariants, } = getValue(); return isPlaceholder ? ( ) : ( ); }, }, ); }), columnHelper.display({ id: 'actions', header: '', cell: ({ row }) => ( ), enableSorting: false, enableHiding: false, meta: { align: 'right', width: '1%', }, }), ], [ projectId, environments, tableState.favoritesFirst, refetch, isPlaceholder, ], ); const placeholderData = useMemo( () => Array(tableState.limit) .fill(null) .map((_, index) => ({ id: index, type: '-', name: `Feature name ${index}`, description: '', createdAt: new Date().toISOString(), createdBy: { id: 0, name: '', imageUrl: '', }, dependencyType: null, favorite: false, impressionData: false, project: 'project', segments: [], stale: false, environments: [ { name: 'development', enabled: false, type: 'development', }, { name: 'production', enabled: false, type: 'production', }, ], })), [tableState.limit], ); const bodyLoadingRef = useLoading(isPlaceholder); const data = useMemo(() => { if (isPlaceholder) { return placeholderData; } return features; }, [isPlaceholder, JSON.stringify(features)]); const allColumnIds = useMemo( () => columns.map((column) => column.id).filter(Boolean) as string[], [columns], ); const defaultColumnVisibility = useDefaultColumnVisibility(allColumnIds); const table = useReactTable( withTableState(tableState, setTableState, { columns, data, enableRowSelection: true, state: { columnVisibility: defaultColumnVisibility, }, getRowId, }), ); const { columnVisibility, rowSelection } = table.getState(); const onToggleColumnVisibility = useCallback( (columnId: string) => { const isVisible = columnVisibility[columnId]; const newColumnVisibility: Record = { ...columnVisibility, [columnId]: !isVisible, }; setTableState({ columns: Object.keys(newColumnVisibility).filter( (columnId) => newColumnVisibility[columnId] && !columnId.includes(','), ), }); }, [columnVisibility, setTableState], ); const selectedData = useSelectedData(features, rowSelection); return ( } /> { setTableState({ query }); }} dataToExport={data} environmentsToExport={environments} actions={ ({ header: environment, id: formatEnvironmentColumnId( environment, ), isVisible: columnVisibility[ formatEnvironmentColumnId( environment, ) ], })), ]} onToggle={onToggleColumnVisibility} /> } /> } bodyClass='noop' style={{ cursor: 'inherit' }} >
} /> {rowActionsDialogs} {featureToggleModals} {}} project={projectId} environments={environments} />
); };