Board improvements (#3694)

* New board improvements

* Improve board

* Fix
This commit is contained in:
Charles Bochet 2024-01-30 15:24:03 +01:00 committed by GitHub
parent 511627ccb8
commit f68de1a299
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 205 additions and 54 deletions

View File

@ -126,7 +126,7 @@
"react-error-boundary": "^4.0.11", "react-error-boundary": "^4.0.11",
"react-helmet-async": "^1.3.0", "react-helmet-async": "^1.3.0",
"react-hook-form": "^7.45.1", "react-hook-form": "^7.45.1",
"react-hotkeys-hook": "^4.4.3", "react-hotkeys-hook": "^4.4.4",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-intersection-observer": "^9.5.2", "react-intersection-observer": "^9.5.2",
"react-loading-skeleton": "^3.3.1", "react-loading-skeleton": "^3.3.1",

View File

@ -1,6 +1,7 @@
import { useMemo, useRef, useState } from 'react'; import { useMemo, useRef, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
@ -124,7 +125,7 @@ export const CommandMenu = () => {
); );
useScopedHotkeys( useScopedHotkeys(
'esc', [Key.Escape],
() => { () => {
closeCommandMenu(); closeCommandMenu();
}, },

View File

@ -1,4 +1,5 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -22,6 +23,7 @@ export const KeyboardShortcutMenu = () => {
isKeyboardShortcutMenuOpenedState, isKeyboardShortcutMenuOpenedState,
); );
const { closeCommandMenu } = useCommandMenu(); const { closeCommandMenu } = useCommandMenu();
useScopedHotkeys( useScopedHotkeys(
'shift+?,meta+?', 'shift+?,meta+?',
() => { () => {
@ -33,12 +35,12 @@ export const KeyboardShortcutMenu = () => {
); );
useScopedHotkeys( useScopedHotkeys(
'esc', [Key.Escape],
() => { () => {
closeKeyboardShortcutMenu(); closeKeyboardShortcutMenu();
}, },
AppHotkeyScope.KeyboardShortcutMenu, AppHotkeyScope.KeyboardShortcutMenuOpen,
[toggleKeyboardShortcutMenu], [closeKeyboardShortcutMenu],
); );
return ( return (
@ -46,8 +48,8 @@ export const KeyboardShortcutMenu = () => {
{isKeyboardShortcutMenuOpened && ( {isKeyboardShortcutMenuOpened && (
<KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}> <KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}>
<KeyboardMenuGroup heading="Table"> <KeyboardMenuGroup heading="Table">
{keyboardShortcutsTable.map((TableShortcut) => ( {keyboardShortcutsTable.map((TableShortcut, index) => (
<KeyboardMenuItem shortcut={TableShortcut} /> <KeyboardMenuItem shortcut={TableShortcut} key={index} />
))} ))}
</KeyboardMenuGroup> </KeyboardMenuGroup>
<KeyboardMenuGroup heading="General"> <KeyboardMenuGroup heading="General">

View File

@ -12,9 +12,7 @@ import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-boa
import { useSetRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useSetRecordBoardDeprecatedCardSelectedInternal'; import { useSetRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useSetRecordBoardDeprecatedCardSelectedInternal';
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope'; import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
import { Opportunity } from '@/pipeline/types/Opportunity'; import { Opportunity } from '@/pipeline/types/Opportunity';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { logError } from '~/utils/logError'; import { logError } from '~/utils/logError';
@ -129,12 +127,6 @@ export const RecordBoardDeprecated = ({
const boardRef = useRef<HTMLDivElement>(null); const boardRef = useRef<HTMLDivElement>(null);
useScopedHotkeys(
'escape',
unselectAllActiveCards,
PageHotkeyScope.OpportunitiesPage,
);
return ( return (
<RecordBoardDeprecatedScope recordBoardScopeId={recordBoardId}> <RecordBoardDeprecatedScope recordBoardScopeId={recordBoardId}>
<RecordBoardDeprecatedContextMenu /> <RecordBoardDeprecatedContextMenu />

View File

@ -69,7 +69,9 @@ export const RecordBoardDeprecatedColumnDropdownMenu = ({
useScopedHotkeys( useScopedHotkeys(
[Key.Escape, Key.Enter], [Key.Escape, Key.Enter],
closeMenu, () => {
closeMenu();
},
BoardColumnHotkeyScope.BoardColumn, BoardColumnHotkeyScope.BoardColumn,
[], [],
); );

View File

@ -123,7 +123,7 @@ export const RecordBoardDeprecatedOptionsDropdownContent = ({
); );
useScopedHotkeys( useScopedHotkeys(
Key.Escape, [Key.Escape],
() => { () => {
setViewEditMode('none'); setViewEditMode('none');
closeDropdown(); closeDropdown();

View File

@ -2,15 +2,19 @@ import { useContext, useRef } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // 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 { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // 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, useRecoilValue } from 'recoil'; import { useRecoilCallback, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn'; import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn';
import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export type RecordBoardProps = { export type RecordBoardProps = {
recordBoardId: string; recordBoardId: string;
@ -40,17 +44,25 @@ const StyledBoardHeader = styled.div`
`; `;
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => { export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
const { updateOneRecord, objectMetadataItem } = const { updateOneRecord, selectFieldMetadataItem } =
useContext(RecordBoardContext); useContext(RecordBoardContext);
const boardRef = useRef<HTMLDivElement>(null); const boardRef = useRef<HTMLDivElement>(null);
const { getColumnIdsState, columnsFamilySelector } = const { getColumnIdsState, columnsFamilySelector } =
useRecordBoardStates(recordBoardId); useRecordBoardStates(recordBoardId);
const columnIds = useRecoilValue(getColumnIdsState()); const columnIds = useRecoilValue(getColumnIdsState());
const selectFieldMetadataItem = objectMetadataItem.fields.find( const { resetRecordSelection, setRecordAsSelected } =
(field) => field.type === FieldMetadataType.Select, useRecordBoardSelection(recordBoardId);
);
useListenClickOutsideByClassName({
classNames: ['record-board-card'],
excludeClassNames: ['action-bar', 'context-menu'],
callback: resetRecordSelection,
});
useScopedHotkeys([Key.Escape], resetRecordSelection, TableHotkeyScope.Table);
const onDragEnd: OnDragEndResponder = useRecoilCallback( const onDragEnd: OnDragEndResponder = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
@ -106,7 +118,8 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
</ScrollWrapper> </ScrollWrapper>
<DragSelect <DragSelect
dragSelectable={boardRef} dragSelectable={boardRef}
onDragSelectionChange={() => {}} onDragSelectionStart={resetRecordSelection}
onDragSelectionChange={setRecordAsSelected}
/> />
</StyledWrapper> </StyledWrapper>
</RecordBoardScope> </RecordBoardScope>

View File

@ -1,10 +1,12 @@
import { createContext } from 'react'; import { createContext } from 'react';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
type RecordBoardContextProps = { type RecordBoardContextProps = {
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
selectFieldMetadataItem: FieldMetadataItem;
createOneRecord: (recordInput: Partial<ObjectRecord>) => void; createOneRecord: (recordInput: Partial<ObjectRecord>) => void;
updateOneRecord: ({ updateOneRecord: ({
idToUpdate, idToUpdate,

View File

@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
export const useResetBoardRecordSelection = (recordBoardId?: string) => { export const useRecordBoardSelection = (recordBoardId?: string) => {
const { getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } = const { getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } =
useRecordBoardStates(recordBoardId); useRecordBoardStates(recordBoardId);
@ -20,5 +20,21 @@ export const useResetBoardRecordSelection = (recordBoardId?: string) => {
[getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState], [getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState],
); );
return { resetRecordSelection }; const setRecordAsSelected = useRecoilCallback(
({ snapshot, set }) =>
(recordId: string, isSelected: boolean) => {
const isRecordCurrentlySelected = snapshot
.getLoadable(isRecordBoardCardSelectedFamilyState(recordId))
.getValue();
if (isRecordCurrentlySelected === isSelected) {
return;
}
set(isRecordBoardCardSelectedFamilyState(recordId), isSelected);
},
[isRecordBoardCardSelectedFamilyState],
);
return { resetRecordSelection, setRecordAsSelected };
}; };

View File

@ -18,7 +18,7 @@ export const RecordBoardCardDraggableContainer = ({
{...draggableProvided?.dragHandleProps} {...draggableProvided?.dragHandleProps}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps} {...draggableProvided?.draggableProps}
className="entity-board-card" className="record-board-card"
data-selectable-id={recordId} data-selectable-id={recordId}
data-select-disable data-select-disable
> >

View File

@ -14,6 +14,7 @@ const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
isFirstColumn ? 'none' : theme.border.color.light}; isFirstColumn ? 'none' : theme.border.color.light};
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: fit-content;
max-width: 200px; max-width: 200px;
min-width: 200px; min-width: 200px;
@ -60,6 +61,7 @@ export const RecordBoardColumn = ({
columnDefinition: columnDefinition, columnDefinition: columnDefinition,
isFirstColumn: isFirstColumn, isFirstColumn: isFirstColumn,
isLastColumn: isLastColumn, isLastColumn: isLastColumn,
recordCount: recordIds.length,
}} }}
> >
<Droppable droppableId={recordBoardColumnId}> <Droppable droppableId={recordBoardColumnId}>

View File

@ -1,8 +1,10 @@
import React from 'react'; import React, { useContext } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DroppableProvided } from '@hello-pangea/dnd'; import { Draggable, DroppableProvided } from '@hello-pangea/dnd';
import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo'; import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo';
import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
const StyledPlaceholder = styled.div` const StyledPlaceholder = styled.div`
min-height: 1px; min-height: 1px;
@ -14,6 +16,10 @@ const StyledColumnCardsContainer = styled.div`
flex-direction: column; flex-direction: column;
`; `;
const StyledNewButtonContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(4)};
`;
type RecordBoardColumnCardsContainerProps = { type RecordBoardColumnCardsContainerProps = {
recordIds: string[]; recordIds: string[];
droppableProvided: DroppableProvided; droppableProvided: DroppableProvided;
@ -23,6 +29,8 @@ export const RecordBoardColumnCardsContainer = ({
recordIds, recordIds,
droppableProvided, droppableProvided,
}: RecordBoardColumnCardsContainerProps) => { }: RecordBoardColumnCardsContainerProps) => {
const { columnDefinition } = useContext(RecordBoardColumnContext);
return ( return (
<StyledColumnCardsContainer <StyledColumnCardsContainer
ref={droppableProvided?.innerRef} ref={droppableProvided?.innerRef}
@ -31,6 +39,23 @@ export const RecordBoardColumnCardsContainer = ({
> >
<RecordBoardColumnCardsMemo recordIds={recordIds} /> <RecordBoardColumnCardsMemo recordIds={recordIds} />
<StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder> <StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder>
<Draggable
draggableId={`new-${columnDefinition.id}`}
index={recordIds.length}
isDragDisabled={true}
>
{(draggableProvided) => (
<div
ref={draggableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
>
<StyledNewButtonContainer>
<RecordBoardColumnNewButton />
</StyledNewButtonContainer>
</div>
)}
</Draggable>
</StyledColumnCardsContainer> </StyledColumnCardsContainer>
); );
}; };

View File

@ -46,7 +46,9 @@ export const RecordBoardColumnHeader = () => {
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false); const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
const [isHeaderHovered, setIsHeaderHovered] = useState(false); const [isHeaderHovered, setIsHeaderHovered] = useState(false);
const { columnDefinition } = useContext(RecordBoardColumnContext); const { columnDefinition, recordCount } = useContext(
RecordBoardColumnContext,
);
const { const {
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,
@ -66,7 +68,6 @@ export const RecordBoardColumnHeader = () => {
}; };
const boardColumnTotal = 0; const boardColumnTotal = 0;
const cardIds = [];
return ( return (
<> <>
@ -81,7 +82,7 @@ export const RecordBoardColumnHeader = () => {
/> />
{!!boardColumnTotal && <StyledAmount>${boardColumnTotal}</StyledAmount>} {!!boardColumnTotal && <StyledAmount>${boardColumnTotal}</StyledAmount>}
{!isHeaderHovered && ( {!isHeaderHovered && (
<StyledNumChildren>{cardIds.length}</StyledNumChildren> <StyledNumChildren>{recordCount}</StyledNumChildren>
)} )}
{isHeaderHovered && ( {isHeaderHovered && (
<StyledHeaderActions> <StyledHeaderActions>

View File

@ -0,0 +1,44 @@
import { useContext } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { IconPlus } from '@/ui/display/icon/index';
const StyledButton = styled.button`
align-items: center;
align-self: baseline;
background-color: ${({ theme }) => theme.background.primary};
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.tertiary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(1)};
&:hover {
background-color: ${({ theme }) => theme.background.tertiary};
}
`;
export const RecordBoardColumnNewButton = () => {
const theme = useTheme();
const { columnDefinition } = useContext(RecordBoardColumnContext);
const { createOneRecord, selectFieldMetadataItem } =
useContext(RecordBoardContext);
const onNewClick = () => {
createOneRecord({
[selectFieldMetadataItem.name]: columnDefinition.value,
});
};
return (
<StyledButton onClick={onNewClick}>
<IconPlus size={theme.icon.size.md} />
New
</StyledButton>
);
};

View File

@ -6,6 +6,7 @@ type RecordBoardColumnContextProps = {
columnDefinition: RecordBoardColumnDefinition; columnDefinition: RecordBoardColumnDefinition;
isFirstColumn: boolean; isFirstColumn: boolean;
isLastColumn: boolean; isLastColumn: boolean;
recordCount: number;
}; };
export const RecordBoardColumnContext = export const RecordBoardColumnContext =

View File

@ -1,3 +1,5 @@
import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -41,7 +43,7 @@ export const useRegisterInputEvents = <T>({
); );
useScopedHotkeys( useScopedHotkeys(
'esc', [Key.Escape],
() => { () => {
onEscape?.(inputValue); onEscape?.(inputValue);
}, },

View File

@ -6,6 +6,7 @@ import { RecordBoardActionBar } from '@/object-record/record-board/action-bar/co
import { RecordBoard } from '@/object-record/record-board/components/RecordBoard'; import { RecordBoard } from '@/object-record/record-board/components/RecordBoard';
import { RecordBoardContextMenu } from '@/object-record/record-board/context-menu/components/RecordBoardContextMenu'; import { RecordBoardContextMenu } from '@/object-record/record-board/context-menu/components/RecordBoardContextMenu';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type RecordIndexBoardContainerProps = { type RecordIndexBoardContainerProps = {
recordBoardId: string; recordBoardId: string;
@ -20,14 +21,23 @@ export const RecordIndexBoardContainer = ({
}: RecordIndexBoardContainerProps) => { }: RecordIndexBoardContainerProps) => {
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular }); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.type === FieldMetadataType.Select,
);
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular }); const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular }); const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
const { createOneRecord } = useCreateOneRecord({ objectNameSingular }); const { createOneRecord } = useCreateOneRecord({ objectNameSingular });
if (!selectFieldMetadataItem) {
return;
}
return ( return (
<RecordBoardContext.Provider <RecordBoardContext.Provider
value={{ value={{
objectMetadataItem, objectMetadataItem,
selectFieldMetadataItem,
createOneRecord, createOneRecord,
updateOneRecord, updateOneRecord,
deleteOneRecord, deleteOneRecord,

View File

@ -5,8 +5,9 @@ import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar'; import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar';
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
import { useResetBoardRecordSelection } from '@/object-record/record-board/hooks/useResetBoardRecordSelection'; import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
import { useLoadRecordIndexBoard } from '@/object-record/record-index/hooks/useLoadRecordIndexBoard'; import { useLoadRecordIndexBoard } from '@/object-record/record-index/hooks/useLoadRecordIndexBoard';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata'; import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata';
type RecordIndexBoardContainerEffectProps = { type RecordIndexBoardContainerEffectProps = {
@ -18,12 +19,13 @@ type RecordIndexBoardContainerEffectProps = {
export const RecordIndexBoardContainerEffect = ({ export const RecordIndexBoardContainerEffect = ({
objectNameSingular, objectNameSingular,
recordBoardId, recordBoardId,
viewBarId,
}: RecordIndexBoardContainerEffectProps) => { }: RecordIndexBoardContainerEffectProps) => {
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
}); });
useLoadRecordIndexBoard(objectNameSingular, recordBoardId); useLoadRecordIndexBoard({ objectNameSingular, recordBoardId, viewBarId });
const navigate = useNavigate(); const navigate = useNavigate();
@ -31,9 +33,13 @@ export const RecordIndexBoardContainerEffect = ({
navigate(`/settings/objects/${objectMetadataItem.namePlural}`); navigate(`/settings/objects/${objectMetadataItem.namePlural}`);
}, [navigate, objectMetadataItem.namePlural]); }, [navigate, objectMetadataItem.namePlural]);
const { setColumns, setObjectSingularName, getSelectedRecordIdsSelector } = const {
useRecordBoard(recordBoardId); setColumns,
const { resetRecordSelection } = useResetBoardRecordSelection(recordBoardId); setObjectSingularName,
getSelectedRecordIdsSelector,
setFieldDefinitions,
} = useRecordBoard(recordBoardId);
const { resetRecordSelection } = useRecordBoardSelection(recordBoardId);
useEffect(() => { useEffect(() => {
setObjectSingularName(objectNameSingular); setObjectSingularName(objectNameSingular);
@ -53,6 +59,14 @@ export const RecordIndexBoardContainerEffect = ({
setColumns, setColumns,
]); ]);
const recordIndexFieldDefinitions = useRecoilValue(
recordIndexFieldDefinitionsState,
);
useEffect(() => {
setFieldDefinitions(recordIndexFieldDefinitions);
}, [objectMetadataItem, setFieldDefinitions, recordIndexFieldDefinitions]);
const selectedRecordIds = useRecoilValue(getSelectedRecordIdsSelector()); const selectedRecordIds = useRecoilValue(getSelectedRecordIdsSelector());
const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({ const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({

View File

@ -10,11 +10,19 @@ import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/s
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState'; import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { useSetRecordInStore } from '@/object-record/record-store/hooks/useSetRecordInStore'; import { useSetRecordInStore } from '@/object-record/record-store/hooks/useSetRecordInStore';
import { useViewBar } from '@/views/hooks/useViewBar';
export const useLoadRecordIndexBoard = ( type UseLoadRecordIndexBoardProps = {
objectNameSingular: string, objectNameSingular: string;
recordBoardId: string, viewBarId: string;
) => { recordBoardId: string;
};
export const useLoadRecordIndexBoard = ({
objectNameSingular,
viewBarId,
recordBoardId,
}: UseLoadRecordIndexBoardProps) => {
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
}); });
@ -47,6 +55,10 @@ export const useLoadRecordIndexBoard = (
orderBy, orderBy,
}); });
const { setEntityCountInCurrentView } = useViewBar({
viewBarId,
});
useEffect(() => { useEffect(() => {
setRecordIdsInBoard(records); setRecordIdsInBoard(records);
}, [records, setRecordIdsInBoard]); }, [records, setRecordIdsInBoard]);
@ -55,6 +67,10 @@ export const useLoadRecordIndexBoard = (
setRecordsInStore(records); setRecordsInStore(records);
}, [records, setRecordsInStore]); }, [records, setRecordsInStore]);
useEffect(() => {
setEntityCountInCurrentView(records.length);
}, [records.length, setEntityCountInCurrentView]);
return { return {
records, records,
loading, loading,

View File

@ -1,3 +1,5 @@
import { Key } from 'ts-key-enum';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -28,7 +30,7 @@ export const RecordTableInternalEffect = ({
}); });
useScopedHotkeys( useScopedHotkeys(
'escape', [Key.Escape],
() => { () => {
resetTableRowSelection(); resetTableRowSelection();
}, },

View File

@ -80,7 +80,7 @@ export const TableOptionsDropdownContent = ({
const resetMenu = () => setCurrentMenu(undefined); const resetMenu = () => setCurrentMenu(undefined);
useScopedHotkeys( useScopedHotkeys(
Key.Escape, [Key.Escape],
() => { () => {
closeDropdown(); closeDropdown();
}, },

View File

@ -59,7 +59,7 @@ export const SingleEntitySelectMenuItems = ({
); );
useScopedHotkeys( useScopedHotkeys(
Key.Escape, [Key.Escape],
() => { () => {
onCancel?.(); onCancel?.();
}, },

View File

@ -94,7 +94,7 @@ export const DoubleTextInput = ({
); );
useScopedHotkeys( useScopedHotkeys(
Key.Escape, [Key.Escape],
() => { () => {
onEscape({ onEscape({
firstValue: firstInternalValue, firstValue: firstInternalValue,

View File

@ -2,6 +2,7 @@ import { useState } from 'react';
import { HotkeysEvent } from 'react-hotkeys-hook/dist/types'; import { HotkeysEvent } from 'react-hotkeys-hook/dist/types';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { IconArrowRight } from '@/ui/display/icon/index'; import { IconArrowRight } from '@/ui/display/icon/index';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
@ -151,7 +152,7 @@ export const AutosizeTextInput = ({
); );
useScopedHotkeys( useScopedHotkeys(
'esc', Key.Escape,
(event: KeyboardEvent) => { (event: KeyboardEvent) => {
if (!isFocused) { if (!isFocused) {
return; return;

View File

@ -94,7 +94,7 @@ export const Dropdown = ({
}); });
useScopedHotkeys( useScopedHotkeys(
Key.Escape, [Key.Escape],
() => { () => {
closeDropdown(); closeDropdown();
}, },

View File

@ -66,7 +66,10 @@ export const RightDrawer = () => {
useScopedHotkeys( useScopedHotkeys(
[Key.Escape], [Key.Escape],
() => closeRightDrawer(),
() => {
closeRightDrawer();
},
RightDrawerHotkeyScope.RightDrawer, RightDrawerHotkeyScope.RightDrawer,
[setIsRightDrawerOpen], [setIsRightDrawerOpen],
); );

View File

@ -4,4 +4,5 @@ export enum AppHotkeyScope {
CommandMenu = 'command-menu', CommandMenu = 'command-menu',
CommandMenuOpen = 'command-menu-open', CommandMenuOpen = 'command-menu-open',
KeyboardShortcutMenu = 'keyboard-shortcut-menu', KeyboardShortcutMenu = 'keyboard-shortcut-menu',
KeyboardShortcutMenuOpen = 'keyboard-shortcut-menu-open',
} }

View File

@ -127,8 +127,9 @@ export const useViewBar = (props?: UseViewProps) => {
if (!isDeeplyEqual(savedViewFields, queriedViewFields)) { if (!isDeeplyEqual(savedViewFields, queriedViewFields)) {
set(currentViewFieldsState, queriedViewFields); set(currentViewFieldsState, queriedViewFields);
set(savedViewFieldsState, queriedViewFields); set(savedViewFieldsState, queriedViewFields);
onViewFieldsChange?.(queriedViewFields);
} }
onViewFieldsChange?.(queriedViewFields);
}, },
[scopeId], [scopeId],
); );

View File

@ -38381,13 +38381,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-hotkeys-hook@npm:^4.4.3": "react-hotkeys-hook@npm:^4.4.4":
version: 4.4.3 version: 4.4.4
resolution: "react-hotkeys-hook@npm:4.4.3" resolution: "react-hotkeys-hook@npm:4.4.4"
peerDependencies: peerDependencies:
react: ">=16.8.1" react: ">=16.8.1"
react-dom: ">=16.8.1" react-dom: ">=16.8.1"
checksum: ef79e279129f6e55d81c8762b1da214d9c6ee4617b9597dbc3f93057cba8d166831508967b8e3f763a0c4ce0af3b59e6888c6fc94d152deac9335020b2ba80df checksum: afe7418c8bd0ecd3a3f315648b84d9978d06a02e55f93df23aaa00e56613fca839f9ff4d10387e8336454702b4aa03cef97f3e67bfe3e121e42f16d84685f55a
languageName: node languageName: node
linkType: hard linkType: hard
@ -43453,7 +43453,7 @@ __metadata:
react-error-boundary: "npm:^4.0.11" react-error-boundary: "npm:^4.0.11"
react-helmet-async: "npm:^1.3.0" react-helmet-async: "npm:^1.3.0"
react-hook-form: "npm:^7.45.1" react-hook-form: "npm:^7.45.1"
react-hotkeys-hook: "npm:^4.4.3" react-hotkeys-hook: "npm:^4.4.4"
react-icons: "npm:^4.12.0" react-icons: "npm:^4.12.0"
react-intersection-observer: "npm:^9.5.2" react-intersection-observer: "npm:^9.5.2"
react-loading-skeleton: "npm:^3.3.1" react-loading-skeleton: "npm:^3.3.1"