import React, { useEffect, useRef, useState } from 'react'; import Button from '@leafygreen-ui/button'; // leafygreen-ui import { cx } from '@leafygreen-ui/emotion'; import { usePrevious, useViewportSize } from '@leafygreen-ui/hooks'; // @ts-expect-error import SettingsIcon from '@leafygreen-ui/icon/dist/Settings'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; import { FocusableMenuItem, Menu, MenuItem, MenuSeparator, } from '@leafygreen-ui/menu'; import { breakpoints } from '../../breakpoints'; import { useMongoNavContext } from '../../MongoNavContext'; // mongo-nav import { useOnElementClick } from '../../on-element-click-provider'; import { CurrentOrganizationInterface, Mode, MongoNavInterface, NavElement, OrganizationInterface, PlanType, URLS, } from '../../types'; // mongo-select import Input from '../mongo-select-helpers/Input'; import { onKeyDown } from '../mongo-select-helpers/onKeyDown'; import { menuContainerStyle, menuItemContainerStyle, searchInputStyle, settingsButtonIconActiveThemeStyle, settingsButtonIconDisabledStyle, settingsButtonIconThemeStyle, } from '../styles'; import { BaseMongoSelectProps } from '../types'; import { emptyStateStyle, linkThemeStyle, menuSeparatorStyles, orgMenuContainerStyle, orgSelectWrapperStyle, orgSettingsButtonBaseStyle, orgSettingsButtonThemeStyle, viewAllThemeStyle, } from './OrgSelect.styles'; import { orgSelectSegmentStyle, OrgSelectTrigger } from './OrgSelectTrigger'; // types interface OrganizationMongoSelectProps extends BaseMongoSelectProps { data?: Array; current?: CurrentOrganizationInterface | null; constructOrganizationURL: NonNullable< MongoNavInterface['constructOrganizationURL'] >; isOnPrem?: boolean; isActive?: boolean; disabled?: boolean; urls: Partial; } // eslint-disable-next-line @typescript-eslint/no-unused-vars const formattedPlanTypes: Record = { [PlanType.Atlas]: 'Atlas', [PlanType.Cloud]: 'Cloud Manager', [PlanType.OnPrem]: 'Ops Manager', } as const; interface OrgFilterResponse extends OrganizationInterface { groups: null; } function OrgSelect({ current, mode, hosts = {}, data = [], urls, onChange: onChangeProp, constructOrganizationURL, admin = false, isActive = false, disabled: disabledProp = false, className, }: OrganizationMongoSelectProps) { const [value, setValue] = useState(''); const [filteredData, setFilteredData] = useState(data); const [isFetching, setIsFetching] = useState(false); const [open, setOpen] = useState(false); const wasOpen = usePrevious(open); const onElementClick = useOnElementClick(); const viewportSize = useViewportSize(); const { theme } = useDarkMode(); const { isLoading } = useMongoNavContext(); const buttonRef = useRef(null); const isFiltered = value !== ''; const isAdminSearch = isFiltered && admin && mode === Mode.Production; const isDisabled = isLoading || disabledProp; const renderedData = isFiltered ? filteredData : data; const toggleOpen = () => { setOpen(curr => !curr); if (!open) { setValue(''); } }; const hasOnChangeProp = !!onChangeProp; useEffect(() => { // Skip if we're not filtering if (!isFiltered) { return; } // defer all behavior to user-provided filtering if (hasOnChangeProp) { return; } // serverside filtering (admin users only) if (isAdminSearch) { setIsFetching(true); const controller = new AbortController(); const fetchOrgsAsAdmin = (searchTerm = '') => { const queryString = searchTerm ? `?term=${searchTerm}` : ''; const endpointURI = `${hosts.cloud}/user/shared/organizations/search${queryString}`; return fetch(endpointURI, { credentials: 'include', mode: 'cors', method: 'GET', signal: controller.signal, }); }; fetchOrgsAsAdmin(value) .then(response => response.json()) .then((response: Array) => { const data = response.map(({ groups, ...org }) => org); setFilteredData(data); setIsFetching(false); }) .catch(console.error); return () => { controller.abort(); }; } // clientside filtering (default) const normalizedValue = value.toLowerCase(); const filtered = data?.filter( datum => datum.orgName.toLowerCase().indexOf(normalizedValue) !== -1, ); setFilteredData(filtered); }, [isFiltered, hasOnChangeProp, isAdminSearch, hosts.cloud, data, value]); // on change, make sure value actually changes const onChange = (e: React.ChangeEvent) => { const val = e.target.value.replace(/\\/g, '\\'); setValue(val); if (open === true && wasOpen !== open) { // Opt out of type-checking as we just want to trigger the onElementClick handler onElementClick(NavElement.OrgNavOrgSelectSearch)(e as any); } return onChangeProp?.({ value, setData: setFilteredData, event: e, }); }; const isTablet = viewportSize ? viewportSize.width < breakpoints.medium : false; const renderOrganizationOption = (datum: OrganizationInterface) => { const { orgId, orgName, planType } = datum; const isCurrentOrg = orgId ? orgId === current?.orgId : false; const active = isCurrentOrg && !isDisabled; return ( {orgName} ); }; return (
{/* TODO: Replace this with a SplitButton component when that is available */} {data && ( )} {isAdminSearch && isFetching && ( Searching... )} {!isFetching && renderedData.length === 0 && ( No matches found. )} {renderedData?.map(renderOrganizationOption) ?? ( You do not belong to any organizations. Create an organization on the{' '} Organizations {' '} page. )} {renderedData && ( <> View All Organizations )} {!isDisabled && (
); } OrgSelect.displayName = 'OrgSelect'; export default OrgSelect;