New view picker (#4610)

* Implement new view picker

* Complete feature

* Fixes according to review
This commit is contained in:
Charles Bochet
2024-03-22 15:04:17 +01:00
committed by GitHub
parent d876b40056
commit 4a493b6ecf
61 changed files with 1216 additions and 422 deletions

View File

@ -8,6 +8,7 @@ import { onRecordBoardFetchMoreVisibilityChangeComponentState } from '@/object-r
import { recordBoardColumnIdsComponentState } from '@/object-record/record-board/states/recordBoardColumnIdsComponentState';
import { recordBoardFieldDefinitionsComponentState } from '@/object-record/record-board/states/recordBoardFieldDefinitionsComponentState';
import { recordBoardFiltersComponentState } from '@/object-record/record-board/states/recordBoardFiltersComponentState';
import { recordBoardKanbanFieldMetadataNameComponentState } from '@/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState';
import { recordBoardObjectSingularNameComponentState } from '@/object-record/record-board/states/recordBoardObjectSingularNameComponentState';
import { recordBoardRecordIdsByColumnIdComponentFamilyState } from '@/object-record/record-board/states/recordBoardRecordIdsByColumnIdComponentFamilyState';
import { recordBoardSortsComponentState } from '@/object-record/record-board/states/recordBoardSortsComponentState';
@ -32,6 +33,10 @@ export const useRecordBoardStates = (recordBoardId?: string) => {
recordBoardObjectSingularNameComponentState,
scopeId,
),
kanbanFieldMetadataNameState: extractComponentState(
recordBoardKanbanFieldMetadataNameComponentState,
scopeId,
),
isFetchingRecordState: extractComponentState(
isRecordBoardFetchingRecordsComponentState,
scopeId,

View File

@ -10,6 +10,7 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
recordIdsByColumnIdFamilyState,
columnsFamilySelector,
columnIdsState,
kanbanFieldMetadataNameState,
} = useRecordBoardStates(recordBoardId);
const setRecordIds = useRecoilCallback(
@ -26,8 +27,18 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
.getLoadable(recordIdsByColumnIdFamilyState(columnId))
.getValue();
const kanbanFieldMetadataName = snapshot
.getLoadable(kanbanFieldMetadataNameState)
.getValue();
if (!kanbanFieldMetadataName) {
return;
}
const columnRecordIds = records
.filter((record) => record.stage === column?.value)
.filter(
(record) => record[kanbanFieldMetadataName] === column?.value,
)
.sort(sortRecordsByPosition)
.map((record) => record.id);
@ -36,7 +47,12 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
}
});
},
[columnsFamilySelector, columnIdsState, recordIdsByColumnIdFamilyState],
[
columnIdsState,
columnsFamilySelector,
recordIdsByColumnIdFamilyState,
kanbanFieldMetadataNameState,
],
);
return {

View File

@ -12,12 +12,16 @@ export const useRecordBoard = (recordBoardId?: string) => {
selectedRecordIdsSelector,
isCompactModeActiveState,
onFetchMoreVisibilityChangeState,
kanbanFieldMetadataNameState,
} = useRecordBoardStates(recordBoardId);
const { setColumns } = useSetRecordBoardColumns(recordBoardId);
const { setRecordIds } = useSetRecordBoardRecordIds(recordBoardId);
const setFieldDefinitions = useSetRecoilState(fieldDefinitionsState);
const setObjectSingularName = useSetRecoilState(objectSingularNameState);
const setKanbanFieldMetadataName = useSetRecoilState(
kanbanFieldMetadataNameState,
);
return {
scopeId,
@ -25,6 +29,7 @@ export const useRecordBoard = (recordBoardId?: string) => {
setRecordIds,
setFieldDefinitions,
setObjectSingularName,
setKanbanFieldMetadataName,
selectedRecordIdsSelector,
isCompactModeActiveState,
onFetchMoreVisibilityChangeState,

View File

@ -0,0 +1,7 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const recordBoardKanbanFieldMetadataNameComponentState =
createComponentState<string | undefined>({
key: 'recordBoardKanbanFieldMetadataNameComponentState',
defaultValue: undefined,
});

View File

@ -8,7 +8,10 @@ import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoar
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
import { useLoadRecordIndexBoard } from '@/object-record/record-index/hooks/useLoadRecordIndexBoard';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
type RecordIndexBoardContainerEffectProps = {
objectNameSingular: string;
@ -31,6 +34,7 @@ export const RecordIndexBoardContainerEffect = ({
selectedRecordIdsSelector,
setFieldDefinitions,
onFetchMoreVisibilityChangeState,
setKanbanFieldMetadataName,
} = useRecordBoard(recordBoardId);
const { fetchMoreRecords, loading } = useLoadRecordIndexBoard({
@ -43,6 +47,10 @@ export const RecordIndexBoardContainerEffect = ({
onFetchMoreVisibilityChangeState,
);
const recordIndexKanbanFieldMetadataId = useRecoilValue(
recordIndexKanbanFieldMetadataIdState,
);
useEffect(() => {
setOnFetchMoreVisibilityChange(() => () => {
if (!loading) {
@ -67,6 +75,7 @@ export const RecordIndexBoardContainerEffect = ({
setColumns(
computeRecordBoardColumnDefinitionsFromObjectMetadata(
objectMetadataItem,
recordIndexKanbanFieldMetadataId ?? '',
navigateToSelectSettings,
),
);
@ -74,6 +83,7 @@ export const RecordIndexBoardContainerEffect = ({
navigateToSelectSettings,
objectMetadataItem,
objectNameSingular,
recordIndexKanbanFieldMetadataId,
setColumns,
]);
@ -85,6 +95,24 @@ export const RecordIndexBoardContainerEffect = ({
setFieldDefinitions(recordIndexFieldDefinitions);
}, [objectMetadataItem, setFieldDefinitions, recordIndexFieldDefinitions]);
useEffect(() => {
if (isDefined(recordIndexKanbanFieldMetadataId)) {
const kanbanFieldMetadataName = objectMetadataItem?.fields.find(
(field) =>
field.type === FieldMetadataType.Select &&
field.id === recordIndexKanbanFieldMetadataId,
)?.name;
if (isDefined(kanbanFieldMetadataName)) {
setKanbanFieldMetadataName(kanbanFieldMetadataName);
}
}
}, [
objectMetadataItem,
recordIndexKanbanFieldMetadataId,
setKanbanFieldMetadataName,
]);
const selectedRecordIds = useRecoilValue(selectedRecordIdsSelector());
const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({

View File

@ -10,10 +10,10 @@ import { RecordIndexTableContainer } from '@/object-record/record-index/componen
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
import { RecordIndexViewBarEffect } from '@/object-record/record-index/components/RecordIndexViewBarEffect';
import { RecordIndexOptionsDropdown } from '@/object-record/record-index/options/components/RecordIndexOptionsDropdown';
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexIsCompactModeActiveState } from '@/object-record/record-index/states/recordIndexIsCompactModeActiveState';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
@ -65,6 +65,9 @@ export const RecordIndexContainer = ({
const setRecordIndexIsCompactModeActive = useSetRecoilState(
recordIndexIsCompactModeActiveState,
);
const setRecordIndexViewKanbanFieldMetadataIdState = useSetRecoilState(
recordIndexKanbanFieldMetadataIdState,
);
const { setTableFilters, setTableSorts, setTableColumns } = useRecordTable({
recordTableId: recordIndexId,
@ -129,9 +132,11 @@ export const RecordIndexContainer = ({
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
);
setRecordIndexViewType(view.type);
setRecordIndexViewKanbanFieldMetadataIdState(
view.kanbanFieldMetadataId,
);
setRecordIndexIsCompactModeActive(view.isCompact);
}}
optionsDropdownScopeId={RECORD_INDEX_OPTIONS_DROPDOWN_ID}
/>
<RecordIndexViewBarEffect
objectNamePlural={objectNamePlural}

View File

@ -3,7 +3,6 @@ import { RecordIndexOptionsDropdownContent } from '@/object-record/record-index/
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useViewBarEditMode } from '@/views/hooks/useViewBarEditMode';
import { ViewType } from '@/views/types/ViewType';
type RecordIndexOptionsDropdownProps = {
@ -17,8 +16,6 @@ export const RecordIndexOptionsDropdown = ({
objectNameSingular,
viewType,
}: RecordIndexOptionsDropdownProps) => {
const { setViewEditMode } = useViewBarEditMode(recordIndexId);
return (
<Dropdown
dropdownId={RECORD_INDEX_OPTIONS_DROPDOWN_ID}
@ -32,7 +29,6 @@ export const RecordIndexOptionsDropdown = ({
recordIndexId={recordIndexId}
/>
}
onClickOutside={() => setViewEditMode('none')}
/>
);
};

View File

@ -1,6 +1,5 @@
import { useRef, useState } from 'react';
import { useState } from 'react';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
import { RECORD_INDEX_OPTIONS_DROPDOWN_ID } from '@/object-record/record-index/options/constants/RecordIndexOptionsDropdownId';
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
@ -14,7 +13,6 @@ import {
IconTag,
} from '@/ui/display/icon';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/layout/dropdown/components/DropdownMenuInput';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
@ -23,8 +21,6 @@ import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemTog
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { useHandleViews } from '@/views/hooks/useHandleViews';
import { useViewBarEditMode } from '@/views/hooks/useViewBarEditMode';
import { ViewType } from '@/views/types/ViewType';
type RecordIndexOptionsMenu = 'fields';
@ -40,9 +36,6 @@ export const RecordIndexOptionsDropdownContent = ({
recordIndexId,
objectNameSingular,
}: RecordIndexOptionsDropdownContentProps) => {
const { updateCurrentView, createEmptyView, selectView } =
useHandleViews(recordIndexId);
const { viewEditMode, setViewEditMode } = useViewBarEditMode(recordIndexId);
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
const { closeDropdown } = useDropdown(RECORD_INDEX_OPTIONS_DROPDOWN_ID);
@ -53,8 +46,6 @@ export const RecordIndexOptionsDropdownContent = ({
const resetMenu = () => setCurrentMenu(undefined);
const viewEditInputRef = useRef<HTMLInputElement>(null);
const handleSelectMenu = (option: RecordIndexOptionsMenu) => {
setCurrentMenu(option);
};
@ -67,25 +58,6 @@ export const RecordIndexOptionsDropdownContent = ({
TableOptionsHotkeyScope.Dropdown,
);
useScopedHotkeys(
Key.Enter,
async () => {
const name = viewEditInputRef.current?.value;
if (viewEditMode === 'create') {
const id = v4();
await createEmptyView(id, name ?? '');
selectView(id);
} else {
updateCurrentView({ name });
}
resetMenu();
setViewEditMode('none');
closeDropdown();
},
TableOptionsHotkeyScope.Dropdown,
);
const {
handleColumnVisibilityChange,
handleReorderColumns,
@ -128,37 +100,18 @@ export const RecordIndexOptionsDropdownContent = ({
return (
<>
{!currentMenu && (
<>
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode !== 'none'}
placeholder={
viewEditMode === 'create'
? 'New view'
: viewEditMode === 'edit'
? 'View name'
: ''
}
defaultValue={
viewEditMode === 'create'
? ''
: currentViewWithCombinedFiltersAndSorts?.name
}
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => handleSelectMenu('fields')}
LeftIcon={IconTag}
text="Fields"
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItem
onClick={() => handleSelectMenu('fields')}
LeftIcon={IconTag}
text="Fields"
/>
<MenuItem
onClick={() => openRecordSpreadsheetImport()}
LeftIcon={IconFileImport}
text="Import"
/>
</DropdownMenuItemsContainer>
</>
<MenuItem
onClick={() => openRecordSpreadsheetImport()}
LeftIcon={IconFileImport}
text="Import"
/>
</DropdownMenuItemsContainer>
)}
{currentMenu === 'fields' && (
<>

View File

@ -0,0 +1,8 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const recordIndexKanbanFieldMetadataIdState = createState<string | null>(
{
key: 'recordIndexKanbanFieldMetadataIdState',
defaultValue: null,
},
);

View File

@ -5,10 +5,13 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
export const computeRecordBoardColumnDefinitionsFromObjectMetadata = (
objectMetadataItem: ObjectMetadataItem,
kanbanFieldMetadataId: string,
navigateToSelectSettings: () => void,
): RecordBoardColumnDefinition[] => {
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.type === FieldMetadataType.Select,
(field) =>
field.id === kanbanFieldMetadataId &&
field.type === FieldMetadataType.Select,
);
if (!selectFieldMetadataItem) {