import { useCallback, useMemo, useState } from 'react'; import styled from '@emotion/styled'; import { autoUpdate, flip, offset, size, useFloating, } from '@floating-ui/react'; import { CompanyChip } from '@/companies/components/CompanyChip'; import { useFilteredSearchCompanyQuery } from '@/companies/queries'; import { PersonChip } from '@/people/components/PersonChip'; import { useFilteredSearchPeopleQuery } from '@/people/queries'; import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef'; import { usePreviousHotkeyScope } from '@/ui/hotkey/hooks/usePreviousHotkeyScope'; import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys'; import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope'; import { MultipleEntitySelect } from '@/ui/relation-picker/components/MultipleEntitySelect'; import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope'; import { CommentableType, CommentThread, CommentThreadTarget, } from '~/generated/graphql'; import { useHandleCheckableCommentThreadTargetChange } from '../hooks/useHandleCheckableCommentThreadTargetChange'; import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '../utils/flatMapAndSortEntityForSelectArrayByName'; type OwnProps = { commentThread?: Pick & { commentThreadTargets: Array< Pick >; }; }; const StyledContainer = styled.div` align-items: flex-start; display: flex; flex-direction: row; gap: ${({ theme }) => theme.spacing(2)}; justify-content: flex-start; width: 100%; `; const StyledRelationContainer = styled.div` --horizontal-padding: ${({ theme }) => theme.spacing(1)}; --vertical-padding: ${({ theme }) => theme.spacing(1.5)}; border: 1px solid transparent; cursor: pointer; display: flex; flex-wrap: wrap; gap: ${({ theme }) => theme.spacing(2)}; &:hover { background-color: ${({ theme }) => theme.background.secondary}; border: 1px solid ${({ theme }) => theme.border.color.light}; } min-height: calc(32px - 2 * var(--vertical-padding)); overflow: hidden; padding: var(--vertical-padding) var(--horizontal-padding); width: calc(100% - 2 * var(--horizontal-padding)); `; const StyledMenuWrapper = styled.div` z-index: ${({ theme }) => theme.lastLayerZIndex}; `; export function CommentThreadRelationPicker({ commentThread }: OwnProps) { const [isMenuOpen, setIsMenuOpen] = useState(false); const [searchFilter, setSearchFilter] = useState(''); const [selectedEntityIds, setSelectedEntityIds] = useState< Record >({}); const { setHotkeyScopeAndMemorizePreviousScope, goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); const initialPeopleIds = useMemo( () => commentThread?.commentThreadTargets ?.filter((relation) => relation.commentableType === 'Person') .map((relation) => relation.commentableId) ?? [], [commentThread?.commentThreadTargets], ); const initialCompanyIds = useMemo( () => commentThread?.commentThreadTargets ?.filter((relation) => relation.commentableType === 'Company') .map((relation) => relation.commentableId) ?? [], [commentThread?.commentThreadTargets], ); const initialSelectedEntityIds = useMemo( () => [...initialPeopleIds, ...initialCompanyIds].reduce< Record >((result, entityId) => ({ ...result, [entityId]: true }), {}), [initialPeopleIds, initialCompanyIds], ); const personsForMultiSelect = useFilteredSearchPeopleQuery({ searchFilter, selectedIds: initialPeopleIds, }); const companiesForMultiSelect = useFilteredSearchCompanyQuery({ searchFilter, selectedIds: initialCompanyIds, }); const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([ personsForMultiSelect.selectedEntities, companiesForMultiSelect.selectedEntities, ]); const filteredSelectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([ personsForMultiSelect.filteredSelectedEntities, companiesForMultiSelect.filteredSelectedEntities, ]); const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([ personsForMultiSelect.entitiesToSelect, companiesForMultiSelect.entitiesToSelect, ]); const handleCheckItemsChange = useHandleCheckableCommentThreadTargetChange({ commentThread, }); const exitEditMode = useCallback(() => { goBackToPreviousHotkeyScope(); setIsMenuOpen(false); setSearchFilter(''); if (Object.values(selectedEntityIds).some((value) => !!value)) { handleCheckItemsChange(selectedEntityIds, entitiesToSelect); } }, [ entitiesToSelect, selectedEntityIds, goBackToPreviousHotkeyScope, handleCheckItemsChange, ]); const handleRelationContainerClick = useCallback(() => { if (isMenuOpen) { exitEditMode(); } else { setIsMenuOpen(true); setSelectedEntityIds(initialSelectedEntityIds); setHotkeyScopeAndMemorizePreviousScope( RelationPickerHotkeyScope.RelationPicker, ); } }, [ initialSelectedEntityIds, exitEditMode, isMenuOpen, setHotkeyScopeAndMemorizePreviousScope, ]); useScopedHotkeys( ['esc', 'enter'], () => { exitEditMode(); }, RelationPickerHotkeyScope.RelationPicker, [exitEditMode], ); const { refs, floatingStyles } = useFloating({ strategy: 'absolute', middleware: [ offset(({ rects }) => { return -rects.reference.height; }), flip(), size(), ], whileElementsMounted: autoUpdate, open: isMenuOpen, placement: 'bottom-start', }); useListenClickOutsideArrayOfRef({ refs: [refs.floating, refs.domReference], callback: () => { exitEditMode(); }, }); return ( {selectedEntities?.map((entity) => entity.entityType === CommentableType.Company ? ( ) : ( ), )} {isMenuOpen && ( )} ); }