diff --git a/front/.eslintrc.js b/front/.eslintrc.js index 51c37fcc1..154da81b2 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -21,6 +21,7 @@ module.exports = { { files: ['*.js', '*.jsx', '*.ts', '*.tsx'], rules: { + 'no-control-regex': 0, 'simple-import-sort/imports': [ 'error', { diff --git a/front/src/modules/command-menu/components/CommandMenu.tsx b/front/src/modules/command-menu/components/CommandMenu.tsx index d4d3ed5ea..feaeb2de7 100644 --- a/front/src/modules/command-menu/components/CommandMenu.tsx +++ b/front/src/modules/command-menu/components/CommandMenu.tsx @@ -92,43 +92,6 @@ export function CommandMenu() { cmd.type === CommandType.Create, ); - /* - TODO: Allow performing actions on page through CommandBar - - import { useMatch, useResolvedPath } from 'react-router-dom'; - import { IconBuildingSkyscraper, IconUser } from '@/ui/icon'; - - const createSection = ( - - } - shortcuts={ - !!useMatch({ - path: useResolvedPath('/people').pathname, - end: true, - }) - ? ['C'] - : [] - } - /> - } - shortcuts={ - !!useMatch({ - path: useResolvedPath('/companies').pathname, - end: true, - }) - ? ['C'] - : [] - } - /> - - );*/ - return ( )} @@ -245,7 +209,7 @@ export function CommandMenu() { ) .map((cmd) => ( void; icon?: ReactNode; shortcuts?: Array; diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index 9becd2c03..9af126435 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -2,8 +2,8 @@ import { ReactNode, useContext } from 'react'; import styled from '@emotion/styled'; import { useRecoilState, useRecoilValue } from 'recoil'; +import { useCurrentCardSelected } from '@/ui/board/hooks/useCurrentCardSelected'; import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; -import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState'; import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState'; import { EntityChipVariant } from '@/ui/chip/components/EntityChip'; import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField'; @@ -102,6 +102,8 @@ const StyledFieldContainer = styled.div` `; export function CompanyBoardCard() { + const { currentCardSelected, setCurrentCardSelected } = + useCurrentCardSelected(); const boardCardId = useContext(BoardCardIdContext); const [companyProgress] = useRecoilState( @@ -109,23 +111,8 @@ export function CompanyBoardCard() { ); const { pipelineProgress, company } = companyProgress ?? {}; - const [selectedBoardCards, setSelectedBoardCards] = useRecoilState( - selectedBoardCardIdsState, - ); const viewFieldsDefinitions = useRecoilValue(viewFieldsDefinitionsState); - const selected = selectedBoardCards.includes(boardCardId ?? ''); - - function setSelected(isSelected: boolean) { - if (isSelected) { - setSelectedBoardCards([...selectedBoardCards, boardCardId ?? '']); - } else { - setSelectedBoardCards( - selectedBoardCards.filter((id) => id !== boardCardId), - ); - } - } - // boardCardId check can be moved to a wrapper to avoid unnecessary logic above if (!company || !pipelineProgress || !boardCardId) { return null; @@ -150,8 +137,8 @@ export function CompanyBoardCard() { return ( setSelected(!selected)} + selected={currentCardSelected} + onClick={() => setCurrentCardSelected(!currentCardSelected)} > setSelected(!selected)} + checked={currentCardSelected} + onChange={() => setCurrentCardSelected(!currentCardSelected)} variant={CheckboxVariant.Secondary} /> diff --git a/front/src/modules/companies/components/HooksCompanyBoard.tsx b/front/src/modules/companies/components/HooksCompanyBoard.tsx index ffda2cf6e..5c86847f3 100644 --- a/front/src/modules/companies/components/HooksCompanyBoard.tsx +++ b/front/src/modules/companies/components/HooksCompanyBoard.tsx @@ -4,8 +4,10 @@ import { useRecoilState, useSetRecoilState } from 'recoil'; import { pipelineViewFields } from '@/pipeline/constants/pipelineViewFields'; import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState'; import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState'; +import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState'; import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause'; +import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { PipelineProgressableType, @@ -17,6 +19,7 @@ import { useGetPipelineProgressQuery, useGetPipelinesQuery, } from '~/generated/graphql'; +import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds'; import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns'; @@ -30,8 +33,13 @@ export function HooksCompanyBoard({ const setFieldsDefinitionsState = useSetRecoilState( viewFieldsDefinitionsState, ); + const [, setAvailableFilters] = useRecoilScopedState( + availableFiltersScopedState, + CompanyBoardContext, + ); useEffect(() => { + setAvailableFilters(opportunitiesBoardOptions.filters); setFieldsDefinitionsState(pipelineViewFields); }); diff --git a/front/src/modules/ui/board/components/BoardActionBarButtonDeleteBoardCard.tsx b/front/src/modules/ui/board/components/BoardActionBarButtonDeleteBoardCard.tsx index ed68fe778..40864f980 100644 --- a/front/src/modules/ui/board/components/BoardActionBarButtonDeleteBoardCard.tsx +++ b/front/src/modules/ui/board/components/BoardActionBarButtonDeleteBoardCard.tsx @@ -1,53 +1,40 @@ -import { useRecoilCallback } from 'recoil'; +import { getOperationName } from '@apollo/client/utilities'; +import { useRecoilValue } from 'recoil'; -import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState'; -import { boardColumnsState } from '@/ui/board/states/boardColumnsState'; -import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState'; +import { GET_PIPELINES } from '@/pipeline/queries'; import { IconTrash } from '@/ui/icon/index'; import { EntityTableActionBarButton } from '@/ui/table/action-bar/components/EntityTableActionBarButton'; +import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql'; -export function BoardActionBarButtonDeleteBoardCard({ - onDelete, -}: { - onDelete: (deletedCardIds: string[]) => void; -}) { - const deleteBoardCardIds = useRecoilCallback( - ({ set, snapshot }) => - () => { - const boardCardIdsToDelete = snapshot - .getLoadable(selectedBoardCardIdsState) - .getValue(); +import { useRemoveCardIds } from '../hooks/useRemoveCardIds'; +import { selectedCardIdsSelector } from '../states/selectedCardIdsSelector'; - const boardColumns = snapshot.getLoadable(boardColumnsState).getValue(); +export function BoardActionBarButtonDeleteBoardCard() { + const selectedCardIds = useRecoilValue(selectedCardIdsSelector); + const removeCardIds = useRemoveCardIds(); - for (const boardColumn of boardColumns) { - const boardColumnCardIds = snapshot - .getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id)) - .getValue(); + const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({ + refetchQueries: [getOperationName(GET_PIPELINES) ?? ''], + }); - const newBoardColumnCardIds = boardColumnCardIds.filter( - (cardId) => !boardCardIdsToDelete.includes(cardId), - ); - - if (newBoardColumnCardIds.length !== boardColumnCardIds.length) { - set( - boardCardIdsByColumnIdFamilyState(boardColumn.id), - newBoardColumnCardIds, - ); - } - } - - set(selectedBoardCardIdsState, []); - - return boardCardIdsToDelete; + async function handleDelete() { + await deletePipelineProgress({ + variables: { + ids: selectedCardIds, }, - [], - ); - - async function handleDeleteClick() { - const deletedCardIds = deleteBoardCardIds(); - - onDelete(deletedCardIds); + optimisticResponse: { + __typename: 'Mutation', + deleteManyPipelineProgress: { + count: selectedCardIds.length, + }, + }, + update: (cache) => { + removeCardIds(selectedCardIds); + selectedCardIds.forEach((id) => { + cache.evict({ id: `PipelineProgress:${id}` }); + }); + }, + }); } return ( @@ -55,7 +42,7 @@ export function BoardActionBarButtonDeleteBoardCard({ label="Delete" icon={} type="warning" - onClick={handleDeleteClick} + onClick={() => handleDelete()} /> ); } diff --git a/front/src/modules/ui/board/components/EntityBoard.tsx b/front/src/modules/ui/board/components/EntityBoard.tsx index d681c0e62..6bc6e33b6 100644 --- a/front/src/modules/ui/board/components/EntityBoard.tsx +++ b/front/src/modules/ui/board/components/EntityBoard.tsx @@ -10,7 +10,6 @@ import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext'; import { GET_PIPELINE_PROGRESS } from '@/pipeline/queries'; import { BoardHeader } from '@/ui/board/components/BoardHeader'; import { StyledBoard } from '@/ui/board/components/StyledBoard'; -import { useUpdateBoardCardIds } from '@/ui/board/hooks/useUpdateBoardCardIds'; import { BoardColumnIdContext } from '@/ui/board/states/BoardColumnIdContext'; import { SelectedSortType } from '@/ui/filter-n-sort/types/interface'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; @@ -22,9 +21,10 @@ import { useUpdateOnePipelineProgressStageMutation, } from '~/generated/graphql'; +import { useSetCardSelected } from '../hooks/useSetCardSelected'; +import { useUpdateBoardCardIds } from '../hooks/useUpdateBoardCardIds'; import { BoardColumnContext } from '../states/BoardColumnContext'; import { boardColumnsState } from '../states/boardColumnsState'; -import { selectedBoardCardIdsState } from '../states/selectedBoardCardIdsState'; import { BoardOptions } from '../types/BoardOptions'; import { EntityBoardColumn } from './EntityBoardColumn'; @@ -48,6 +48,7 @@ export function EntityBoard({ onEditColumnTitle: (columnId: string, title: string, color: string) => void; }) { const [boardColumns] = useRecoilState(boardColumnsState); + const setCardSelected = useSetCardSelected(); const theme = useTheme(); const [updatePipelineProgressStage] = @@ -104,19 +105,6 @@ export function EntityBoard({ }); const boardRef = useRef(null); - const [selectedBoardCards, setSelectedBoardCards] = useRecoilState( - selectedBoardCardIdsState, - ); - - function setRowSelectedState(boardCardId: string, selected: boolean) { - if (selected && !selectedBoardCards.includes(boardCardId)) { - setSelectedBoardCards([...selectedBoardCards, boardCardId ?? '']); - } else if (!selected && selectedBoardCards.includes(boardCardId)) { - setSelectedBoardCards( - selectedBoardCards.filter((id) => id !== boardCardId), - ); - } - } return (boardColumns?.length ?? 0) > 0 ? ( @@ -144,7 +132,7 @@ export function EntityBoard({ ) : ( diff --git a/front/src/modules/ui/board/components/EntityBoardActionBar.tsx b/front/src/modules/ui/board/components/EntityBoardActionBar.tsx index 749fc9547..4e2ff52f5 100644 --- a/front/src/modules/ui/board/components/EntityBoardActionBar.tsx +++ b/front/src/modules/ui/board/components/EntityBoardActionBar.tsx @@ -3,13 +3,13 @@ import { useRecoilValue } from 'recoil'; import { ActionBar } from '@/ui/action-bar/components/ActionBar'; -import { selectedBoardCardIdsState } from '../states/selectedBoardCardIdsState'; +import { selectedCardIdsSelector } from '../states/selectedCardIdsSelector'; type OwnProps = { children: React.ReactNode | React.ReactNode[]; }; export function EntityBoardActionBar({ children }: OwnProps) { - const selectedBoardCards = useRecoilValue(selectedBoardCardIdsState); - return {children}; + const selectedCardIds = useRecoilValue(selectedCardIdsSelector); + return {children}; } diff --git a/front/src/modules/ui/board/hooks/useCurrentCardSelected.ts b/front/src/modules/ui/board/hooks/useCurrentCardSelected.ts new file mode 100644 index 000000000..78a17f38d --- /dev/null +++ b/front/src/modules/ui/board/hooks/useCurrentCardSelected.ts @@ -0,0 +1,28 @@ +import { useContext } from 'react'; +import { useRecoilCallback, useRecoilState } from 'recoil'; + +import { BoardCardIdContext } from '../states/BoardCardIdContext'; +import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState'; + +export function useCurrentCardSelected() { + const currentCardId = useContext(BoardCardIdContext); + + const [isCardSelected] = useRecoilState( + isCardSelectedFamilyState(currentCardId ?? ''), + ); + + const setCurrentCardSelected = useRecoilCallback( + ({ set }) => + (selected: boolean) => { + if (!currentCardId) return; + + set(isCardSelectedFamilyState(currentCardId), selected); + }, + [], + ); + + return { + currentCardSelected: isCardSelected, + setCurrentCardSelected, + }; +} diff --git a/front/src/modules/ui/board/hooks/useRemoveCardIds.ts b/front/src/modules/ui/board/hooks/useRemoveCardIds.ts new file mode 100644 index 000000000..871cc887f --- /dev/null +++ b/front/src/modules/ui/board/hooks/useRemoveCardIds.ts @@ -0,0 +1,27 @@ +// Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 +import { useRecoilCallback } from 'recoil'; + +import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState'; +import { boardColumnsState } from '../states/boardColumnsState'; + +export function useRemoveCardIds() { + return useRecoilCallback( + ({ snapshot, set }) => + (cardIdToRemove: string[]) => { + const boardColumns = snapshot + .getLoadable(boardColumnsState) + .valueOrThrow(); + + boardColumns.forEach((boardColumn) => { + const columnCardIds = snapshot + .getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id)) + .valueOrThrow(); + set( + boardCardIdsByColumnIdFamilyState(boardColumn.id), + columnCardIds.filter((cardId) => !cardIdToRemove.includes(cardId)), + ); + }); + }, + [], + ); +} diff --git a/front/src/modules/ui/board/hooks/useResetCardSelection.ts b/front/src/modules/ui/board/hooks/useResetCardSelection.ts new file mode 100644 index 000000000..7bb92cbd6 --- /dev/null +++ b/front/src/modules/ui/board/hooks/useResetCardSelection.ts @@ -0,0 +1,27 @@ +import { useRecoilCallback } from 'recoil'; + +import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState'; +import { boardColumnsState } from '../states/boardColumnsState'; +import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState'; + +export function useResetCardSelection() { + return useRecoilCallback( + ({ snapshot, set }) => + () => { + const boardColumns = snapshot + .getLoadable(boardColumnsState) + .valueOrThrow(); + + const cardIds = boardColumns.flatMap((boardColumn) => + snapshot + .getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id)) + .valueOrThrow(), + ); + + for (const cardId of cardIds) { + set(isCardSelectedFamilyState(cardId), false); + } + }, + [], + ); +} diff --git a/front/src/modules/ui/board/hooks/useSetCardSelected.ts b/front/src/modules/ui/board/hooks/useSetCardSelected.ts new file mode 100644 index 000000000..2bb3763f2 --- /dev/null +++ b/front/src/modules/ui/board/hooks/useSetCardSelected.ts @@ -0,0 +1,9 @@ +import { useRecoilCallback } from 'recoil'; + +import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState'; + +export function useSetCardSelected() { + return useRecoilCallback(({ set }) => (cardId: string, selected: boolean) => { + set(isCardSelectedFamilyState(cardId), selected); + }); +} diff --git a/front/src/modules/ui/board/states/isCardSelectedFamilyState.ts b/front/src/modules/ui/board/states/isCardSelectedFamilyState.ts new file mode 100644 index 000000000..a6402a88e --- /dev/null +++ b/front/src/modules/ui/board/states/isCardSelectedFamilyState.ts @@ -0,0 +1,6 @@ +import { atomFamily } from 'recoil'; + +export const isCardSelectedFamilyState = atomFamily({ + key: 'isCardSelectedFamilyState', + default: false, +}); diff --git a/front/src/modules/ui/board/states/selectedBoardCardIdsState.ts b/front/src/modules/ui/board/states/selectedBoardCardIdsState.ts deleted file mode 100644 index bb2beee46..000000000 --- a/front/src/modules/ui/board/states/selectedBoardCardIdsState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { atom } from 'recoil'; - -export const selectedBoardCardIdsState = atom({ - key: 'selectedBoardCardIdsState', - default: [], -}); diff --git a/front/src/modules/ui/board/states/selectedCardIdsSelector.ts b/front/src/modules/ui/board/states/selectedCardIdsSelector.ts new file mode 100644 index 000000000..ff604d6ef --- /dev/null +++ b/front/src/modules/ui/board/states/selectedCardIdsSelector.ts @@ -0,0 +1,22 @@ +import { selector } from 'recoil'; + +import { boardCardIdsByColumnIdFamilyState } from './boardCardIdsByColumnIdFamilyState'; +import { boardColumnsState } from './boardColumnsState'; +import { isCardSelectedFamilyState } from './isCardSelectedFamilyState'; + +export const selectedCardIdsSelector = selector({ + key: 'selectedCardIdsSelector', + get: ({ get }) => { + const boardColumns = get(boardColumnsState); + + const cardIds = boardColumns.flatMap((boardColumn) => + get(boardCardIdsByColumnIdFamilyState(boardColumn.id)), + ); + + const selectedCardIds = cardIds.filter( + (cardId) => get(isCardSelectedFamilyState(cardId)) === true, + ); + + return selectedCardIds; + }, +}); diff --git a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx index 430c73959..b1f01e1f6 100644 --- a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx +++ b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx @@ -28,7 +28,7 @@ export function DropdownMenuContainer({ }); return ( - + {children} ); diff --git a/front/src/modules/ui/link/components/SocialLink.tsx b/front/src/modules/ui/link/components/SocialLink.tsx index dd4acb392..04603b314 100644 --- a/front/src/modules/ui/link/components/SocialLink.tsx +++ b/front/src/modules/ui/link/components/SocialLink.tsx @@ -30,16 +30,25 @@ export function SocialLink({ children, href, onClick, type }: OwnProps) { let displayValue = children; if (type === 'linkedin') { - const splitUrl = href.split('/'); - const splitName = splitUrl[4].split('-'); - displayValue = splitName[2] - ? `${splitName[0]}-${splitName[1]}` - : splitName[0]; + const matches = href.match( + /(?:https?:\/\/)?(?:www.)?linkedin.com\/(?:in|company)\/([-a-zA-Z0-9@:%_+.~#?&//=]*)/, + ); + if (matches && matches[1]) { + displayValue = matches[1]; + } else { + displayValue = 'LinkedIn'; + } } if (type === 'twitter') { - const splitUrl = href.split('/'); - displayValue = `@${splitUrl[3]}`; + const matches = href.match( + /(?:https?:\/\/)?(?:www.)?twitter.com\/([-a-zA-Z0-9@:%_+.~#?&//=]*)/, + ); + if (matches && matches[1]) { + displayValue = `@${matches[1]}`; + } else { + displayValue = '@twitter'; + } } return ( diff --git a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts b/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts index 8a514f1a8..8da2ee7a5 100644 --- a/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts +++ b/front/src/modules/ui/table/editable-cell/hooks/useEditableCell.ts @@ -1,5 +1,6 @@ import { useContext } from 'react'; +import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; @@ -17,17 +18,20 @@ export function useEditableCell() { const { setCurrentCellInEditMode } = useCurrentCellEditMode(); const setHotkeyScope = useSetHotkeyScope(); + const { setDragSelectionStartEnabled } = useDragSelect(); const closeCurrentCellInEditMode = useCloseCurrentCellInEditMode(); const customCellHotkeyScope = useContext(CellHotkeyScopeContext); function closeEditableCell() { + setDragSelectionStartEnabled(true); closeCurrentCellInEditMode(); setHotkeyScope(TableHotkeyScope.TableSoftFocus); } function openEditableCell() { + setDragSelectionStartEnabled(false); setCurrentCellInEditMode(); if (customCellHotkeyScope) { diff --git a/front/src/modules/ui/utilities/drag-select/components/DragSelect.tsx b/front/src/modules/ui/utilities/drag-select/components/DragSelect.tsx index b0b7c499e..e7dad4f1f 100644 --- a/front/src/modules/ui/utilities/drag-select/components/DragSelect.tsx +++ b/front/src/modules/ui/utilities/drag-select/components/DragSelect.tsx @@ -4,6 +4,8 @@ import { useSelectionContainer, } from '@air/react-drag-to-select'; +import { useDragSelect } from '../hooks/useDragSelect'; + type OwnProps = { dragSelectable: RefObject; onDragSelectionChange: (id: string, selected: boolean) => void; @@ -15,8 +17,12 @@ export function DragSelect({ onDragSelectionChange, onDragSelectionStart, }: OwnProps) { + const { isDragSelectionStartEnabled } = useDragSelect(); const { DragSelection } = useSelectionContainer({ shouldStartSelecting: (target) => { + if (!isDragSelectionStartEnabled()) { + return false; + } if (target instanceof HTMLElement) { let el = target; while (el.parentElement && !el.dataset.selectDisable) { diff --git a/front/src/modules/ui/utilities/drag-select/hooks/useDragSelect.ts b/front/src/modules/ui/utilities/drag-select/hooks/useDragSelect.ts new file mode 100644 index 000000000..6fd600163 --- /dev/null +++ b/front/src/modules/ui/utilities/drag-select/hooks/useDragSelect.ts @@ -0,0 +1,28 @@ +import { useRecoilCallback, useRecoilState } from 'recoil'; + +import { isDragSelectionStartEnabledState } from '../states/internal/isDragSelectionStartEnabledState'; + +export function useDragSelect() { + const [, setIsDragSelectionStartEnabledInternal] = useRecoilState( + isDragSelectionStartEnabledState, + ); + + function setDragSelectionStartEnabled(isEnabled: boolean) { + setIsDragSelectionStartEnabledInternal(isEnabled); + } + + const isDragSelectionStartEnabled = useRecoilCallback( + ({ snapshot }) => + () => { + return snapshot + .getLoadable(isDragSelectionStartEnabledState) + .getValue(); + }, + [], + ); + + return { + isDragSelectionStartEnabled, + setDragSelectionStartEnabled, + }; +} diff --git a/front/src/modules/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState.ts b/front/src/modules/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState.ts new file mode 100644 index 000000000..a8fb73232 --- /dev/null +++ b/front/src/modules/ui/utilities/drag-select/states/internal/isDragSelectionStartEnabledState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const isDragSelectionStartEnabledState = atom({ + key: 'drag-select/isDragSelectionStartEnabledState', + default: true, +}); diff --git a/front/src/pages/opportunities/Opportunities.tsx b/front/src/pages/opportunities/Opportunities.tsx index 4cf3efbb5..c6c599cb3 100644 --- a/front/src/pages/opportunities/Opportunities.tsx +++ b/front/src/pages/opportunities/Opportunities.tsx @@ -1,12 +1,10 @@ import { useCallback, useState } from 'react'; -import { getOperationName } from '@apollo/client/utilities'; import { useTheme } from '@emotion/react'; import { HooksCompanyBoard } from '@/companies/components/HooksCompanyBoard'; import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext'; import { defaultPipelineProgressOrderBy, - GET_PIPELINES, PipelineProgressesSelectedSortType, } from '@/pipeline/queries'; import { BoardActionBarButtonDeleteBoardCard } from '@/ui/board/components/BoardActionBarButtonDeleteBoardCard'; @@ -14,13 +12,11 @@ import { EntityBoard } from '@/ui/board/components/EntityBoard'; import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar'; import { BoardOptionsContext } from '@/ui/board/states/BoardOptionsContext'; import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers'; -import { AvailableFiltersContext } from '@/ui/filter-n-sort/states/AvailableFiltersContext'; import { IconTargetArrow } from '@/ui/icon/index'; import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { PipelineProgressOrderByWithRelationInput, - useDeleteManyPipelineProgressMutation, useUpdatePipelineStageMutation, } from '~/generated/graphql'; import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions'; @@ -67,18 +63,6 @@ export function Opportunities() { }); } - const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({ - refetchQueries: [getOperationName(GET_PIPELINES) ?? ''], - }); - - async function handleDelete(cardIdsToDelete: string[]) { - await deletePipelineProgress({ - variables: { - ids: cardIdsToDelete, - }, - }); - } - return ( - - - - - - - + + + + + diff --git a/front/src/sync-hooks/CommandMenuHook.tsx b/front/src/sync-hooks/CommandMenuHook.tsx index 42860ae3c..b056093ed 100644 --- a/front/src/sync-hooks/CommandMenuHook.tsx +++ b/front/src/sync-hooks/CommandMenuHook.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { useSetRecoilState } from 'recoil'; import { commandMenuCommands } from '@/command-menu/constants/commandMenuCommands'; @@ -7,7 +8,9 @@ export function CommandMenuHook() { const setCommands = useSetRecoilState(commandMenuCommand); const commands = commandMenuCommands; - setCommands(commands); + useEffect(() => { + setCommands(commands); + }, [commands, setCommands]); return <>; } diff --git a/front/src/utils/__tests__/is-url.test.ts b/front/src/utils/__tests__/is-url.test.ts index a5ec5081c..69b106f87 100644 --- a/front/src/utils/__tests__/is-url.test.ts +++ b/front/src/utils/__tests__/is-url.test.ts @@ -37,7 +37,11 @@ describe('isURL', () => { expect(isURL('https://2.com/test/')).toBeTruthy(); }); - it(`should return false if string https://2.com/test/sldkfj!?`, () => { - expect(isURL('https://2.com/test/sldkfj!?')).toBeFalsy(); + it(`should return true if string is https://www.linkedin.com/company/b%C3%B6ke-&-partner-sdft-partmbb/`, () => { + expect( + isURL( + 'https://www.linkedin.com/company/b%C3%B6ke-&-partner-sdft-partmbb/', + ), + ).toBeTruthy(); }); }); diff --git a/front/src/utils/is-url.ts b/front/src/utils/is-url.ts index c43b41870..0b9fe22e9 100644 --- a/front/src/utils/is-url.ts +++ b/front/src/utils/is-url.ts @@ -1,14 +1,10 @@ import { isDefined } from './isDefined'; export function isURL(url: string | undefined | null) { - const pattern = new RegExp( - '^(https?:\\/\\/)?' + - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + - '((\\d{1,3}\\.){3}\\d{1,3}))' + - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + - '(\\?[;&a-z\\d%_.~+=-]*)?' + - '(\\#[-a-z\\d_]*)?$', - 'i', + return ( + isDefined(url) && + url.match( + /^(https?:\/\/)?(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/i, + ) ); - return isDefined(url) && !!pattern.test(url); }