View module refactor with atomic recoil component instance states (#6810)
This PR refactors the view module to implement utils that avoid having to create hooks to inject the scope id in the states, like `useViewStates`, each componentState will know its unique related InstanceContext (which holds the instanceId), and thus will be able to retrieve it itself. We keep the naming componentState as it reflects the fact that those states are tied to instances of a component (or its children). We introduce the instance word where it is needed, in place of scopeId for example, to precise the fact that we handle instances of component state, one for each instance of a component. For example, the currentViewId is a state that is tied to an instance of the ViewBar, but as we can switch between views, we want currentViewId to be a componentState tied to an instance of the ViewBar component. This PR also refactors view filter and sort states to fix this issue : https://github.com/twentyhq/twenty/issues/6837 and other problems involving resetting those states between page navigation. Fixes https://github.com/twentyhq/twenty/issues/6837 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,7 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconChevronLeft, IconLayoutKanban, IconTable, IconX } from 'twenty-ui';
|
||||
import { IconLayoutKanban, IconTable, IconX } from 'twenty-ui';
|
||||
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
@ -11,30 +10,26 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { ViewPickerCreateOrEditButton } from '@/views/view-picker/components/ViewPickerCreateOrEditButton';
|
||||
import { ViewPickerCreateButton } from '@/views/view-picker/components/ViewPickerCreateButton';
|
||||
import { ViewPickerIconAndNameContainer } from '@/views/view-picker/components/ViewPickerIconAndNameContainer';
|
||||
import { ViewPickerSaveButtonContainer } from '@/views/view-picker/components/ViewPickerSaveButtonContainer';
|
||||
import { ViewPickerSelectContainer } from '@/views/view-picker/components/ViewPickerSelectContainer';
|
||||
import { VIEW_PICKER_KANBAN_FIELD_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerKanbanFieldDropdownId';
|
||||
import { VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerViewTypeDropdownId';
|
||||
import { useCreateViewFromCurrentState } from '@/views/view-picker/hooks/useCreateViewFromCurrentState';
|
||||
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { useViewPickerPersistView } from '@/views/view-picker/hooks/useViewPickerPersistView';
|
||||
import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates';
|
||||
|
||||
const StyledIconAndNameContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledSelectContainer = styled.div`
|
||||
display: flex;
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
margin: ${({ theme }) => theme.spacing(1)};
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
user-select: none;
|
||||
`;
|
||||
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
|
||||
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
|
||||
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
|
||||
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
|
||||
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
|
||||
import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState';
|
||||
|
||||
const StyledNoKanbanFieldAvailableContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
@ -44,40 +39,32 @@ const StyledNoKanbanFieldAvailableContainer = styled.div`
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
||||
`;
|
||||
|
||||
const StyledSaveButtonContainer = styled.div`
|
||||
display: flex;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
`;
|
||||
export const ViewPickerCreateOrEditContent = () => {
|
||||
const { viewPickerMode, setViewPickerMode } = useViewPickerMode();
|
||||
const {
|
||||
viewPickerInputNameState,
|
||||
viewPickerSelectedIconState,
|
||||
viewPickerIsPersistingState,
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
viewPickerTypeState,
|
||||
viewPickerIsDirtyState,
|
||||
} = useViewPickerStates();
|
||||
export const ViewPickerContentCreateMode = () => {
|
||||
const { setViewPickerMode } = useViewPickerMode();
|
||||
|
||||
const [viewPickerInputName, setViewPickerInputName] = useRecoilState(
|
||||
viewPickerInputNameState,
|
||||
const [viewPickerInputName, setViewPickerInputName] =
|
||||
useRecoilComponentStateV2(viewPickerInputNameComponentState);
|
||||
|
||||
const [viewPickerSelectedIcon, setViewPickerSelectedIcon] =
|
||||
useRecoilComponentStateV2(viewPickerSelectedIconComponentState);
|
||||
|
||||
const viewPickerIsPersisting = useRecoilComponentValueV2(
|
||||
viewPickerIsPersistingComponentState,
|
||||
);
|
||||
const [viewPickerSelectedIcon, setViewPickerSelectedIcon] = useRecoilState(
|
||||
viewPickerSelectedIconState,
|
||||
const setViewPickerIsDirty = useSetRecoilComponentStateV2(
|
||||
viewPickerIsDirtyComponentState,
|
||||
);
|
||||
const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState);
|
||||
const setViewPickerIsDirty = useSetRecoilState(viewPickerIsDirtyState);
|
||||
|
||||
const [viewPickerKanbanFieldMetadataId, setViewPickerKanbanFieldMetadataId] =
|
||||
useRecoilState(viewPickerKanbanFieldMetadataIdState);
|
||||
useRecoilComponentStateV2(viewPickerKanbanFieldMetadataIdComponentState);
|
||||
|
||||
const [viewPickerType, setViewPickerType] =
|
||||
useRecoilState(viewPickerTypeState);
|
||||
const [viewPickerType, setViewPickerType] = useRecoilComponentStateV2(
|
||||
viewPickerTypeComponentState,
|
||||
);
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { handleCreate, handleUpdate } = useViewPickerPersistView();
|
||||
const { createViewFromCurrentState } = useCreateViewFromCurrentState();
|
||||
|
||||
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
|
||||
|
||||
@ -87,18 +74,15 @@ export const ViewPickerCreateOrEditContent = () => {
|
||||
if (viewPickerIsPersisting) {
|
||||
return;
|
||||
}
|
||||
if (viewPickerMode === 'create') {
|
||||
if (
|
||||
viewPickerType === ViewType.Kanban &&
|
||||
availableFieldsForKanban.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await handleCreate();
|
||||
}
|
||||
if (viewPickerMode === 'edit') {
|
||||
await handleUpdate();
|
||||
|
||||
if (
|
||||
viewPickerType === ViewType.Kanban &&
|
||||
availableFieldsForKanban.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
await createViewFromCurrentState();
|
||||
},
|
||||
ViewsHotkeyScope.ListDropdown,
|
||||
);
|
||||
@ -109,23 +93,17 @@ export const ViewPickerCreateOrEditContent = () => {
|
||||
};
|
||||
|
||||
const handleClose = async () => {
|
||||
if (viewPickerMode === 'edit') {
|
||||
await handleUpdate();
|
||||
}
|
||||
setViewPickerMode('list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
StartIcon={viewPickerMode === 'create' ? IconX : IconChevronLeft}
|
||||
onClick={handleClose}
|
||||
>
|
||||
{viewPickerMode === 'create' ? 'Create view' : 'Edit view'}
|
||||
<DropdownMenuHeader StartIcon={IconX} onClick={handleClose}>
|
||||
Create view
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledIconAndNameContainer>
|
||||
<ViewPickerIconAndNameContainer>
|
||||
<IconPicker
|
||||
onChange={onIconChange}
|
||||
selectedIconKey={viewPickerSelectedIcon}
|
||||
@ -140,33 +118,31 @@ export const ViewPickerCreateOrEditContent = () => {
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
</StyledIconAndNameContainer>
|
||||
{viewPickerMode === 'create' && (
|
||||
<StyledSelectContainer>
|
||||
<Select
|
||||
disableBlur
|
||||
label="View type"
|
||||
fullWidth
|
||||
value={viewPickerType}
|
||||
onChange={(value) => {
|
||||
setViewPickerIsDirty(true);
|
||||
setViewPickerType(value);
|
||||
}}
|
||||
options={[
|
||||
{ value: ViewType.Table, label: 'Table', Icon: IconTable },
|
||||
{
|
||||
value: ViewType.Kanban,
|
||||
label: 'Kanban',
|
||||
Icon: IconLayoutKanban,
|
||||
},
|
||||
]}
|
||||
dropdownId={VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID}
|
||||
/>
|
||||
</StyledSelectContainer>
|
||||
)}
|
||||
{viewPickerType === ViewType.Kanban && viewPickerMode === 'create' && (
|
||||
</ViewPickerIconAndNameContainer>
|
||||
<ViewPickerSelectContainer>
|
||||
<Select
|
||||
disableBlur
|
||||
label="View type"
|
||||
fullWidth
|
||||
value={viewPickerType}
|
||||
onChange={(value) => {
|
||||
setViewPickerIsDirty(true);
|
||||
setViewPickerType(value);
|
||||
}}
|
||||
options={[
|
||||
{ value: ViewType.Table, label: 'Table', Icon: IconTable },
|
||||
{
|
||||
value: ViewType.Kanban,
|
||||
label: 'Kanban',
|
||||
Icon: IconLayoutKanban,
|
||||
},
|
||||
]}
|
||||
dropdownId={VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID}
|
||||
/>
|
||||
</ViewPickerSelectContainer>
|
||||
{viewPickerType === ViewType.Kanban && (
|
||||
<>
|
||||
<StyledSelectContainer>
|
||||
<ViewPickerSelectContainer>
|
||||
<Select
|
||||
disableBlur
|
||||
label="Stages"
|
||||
@ -186,7 +162,7 @@ export const ViewPickerCreateOrEditContent = () => {
|
||||
}
|
||||
dropdownId={VIEW_PICKER_KANBAN_FIELD_DROPDOWN_ID}
|
||||
/>
|
||||
</StyledSelectContainer>
|
||||
</ViewPickerSelectContainer>
|
||||
{availableFieldsForKanban.length === 0 && (
|
||||
<StyledNoKanbanFieldAvailableContainer>
|
||||
Set up a Select field on Companies to create a Kanban
|
||||
@ -197,9 +173,9 @@ export const ViewPickerCreateOrEditContent = () => {
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledSaveButtonContainer>
|
||||
<ViewPickerCreateOrEditButton />
|
||||
</StyledSaveButtonContainer>
|
||||
<ViewPickerSaveButtonContainer>
|
||||
<ViewPickerCreateButton />
|
||||
</ViewPickerSaveButtonContainer>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
@ -0,0 +1,100 @@
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconChevronLeft } from 'twenty-ui';
|
||||
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
|
||||
import { ViewPickerEditButton } from '@/views/view-picker/components/ViewPickerEditButton';
|
||||
import { ViewPickerIconAndNameContainer } from '@/views/view-picker/components/ViewPickerIconAndNameContainer';
|
||||
import { ViewPickerSaveButtonContainer } from '@/views/view-picker/components/ViewPickerSaveButtonContainer';
|
||||
import { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
|
||||
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
|
||||
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
|
||||
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
|
||||
|
||||
export const ViewPickerContentEditMode = () => {
|
||||
const { setViewPickerMode } = useViewPickerMode();
|
||||
|
||||
const [viewPickerInputName, setViewPickerInputName] =
|
||||
useRecoilComponentStateV2(viewPickerInputNameComponentState);
|
||||
|
||||
const [viewPickerSelectedIcon, setViewPickerSelectedIcon] =
|
||||
useRecoilComponentStateV2(viewPickerSelectedIconComponentState);
|
||||
|
||||
const viewPickerIsPersisting = useRecoilComponentValueV2(
|
||||
viewPickerIsPersistingComponentState,
|
||||
);
|
||||
const setViewPickerIsDirty = useSetRecoilComponentStateV2(
|
||||
viewPickerIsDirtyComponentState,
|
||||
);
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
async () => {
|
||||
if (viewPickerIsPersisting) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateViewFromCurrentState();
|
||||
},
|
||||
ViewsHotkeyScope.ListDropdown,
|
||||
);
|
||||
|
||||
const onIconChange = ({ iconKey }: { iconKey: string }) => {
|
||||
setViewPickerIsDirty(true);
|
||||
setViewPickerSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
const handleClose = async () => {
|
||||
await updateViewFromCurrentState();
|
||||
|
||||
setViewPickerMode('list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={handleClose}>
|
||||
Edit view
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<ViewPickerIconAndNameContainer>
|
||||
<IconPicker
|
||||
onChange={onIconChange}
|
||||
selectedIconKey={viewPickerSelectedIcon}
|
||||
disableBlur
|
||||
onClose={() => setHotkeyScope(ViewsHotkeyScope.ListDropdown)}
|
||||
/>
|
||||
<DropdownMenuInput
|
||||
value={viewPickerInputName}
|
||||
onChange={(event) => {
|
||||
setViewPickerIsDirty(true);
|
||||
setViewPickerInputName(event.target.value);
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
</ViewPickerIconAndNameContainer>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<ViewPickerSaveButtonContainer>
|
||||
<ViewPickerEditButton />
|
||||
</ViewPickerSaveButtonContainer>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,90 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
|
||||
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
|
||||
import { viewPickerIsDirtyComponentState } from '@/views/view-picker/states/viewPickerIsDirtyComponentState';
|
||||
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
|
||||
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
|
||||
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
|
||||
import { viewPickerSelectedIconComponentState } from '@/views/view-picker/states/viewPickerSelectedIconComponentState';
|
||||
import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const ViewPickerContentEffect = () => {
|
||||
const setViewPickerSelectedIcon = useSetRecoilComponentStateV2(
|
||||
viewPickerSelectedIconComponentState,
|
||||
);
|
||||
const setViewPickerInputName = useSetRecoilComponentStateV2(
|
||||
viewPickerInputNameComponentState,
|
||||
);
|
||||
|
||||
const [viewPickerKanbanFieldMetadataId, setViewPickerKanbanFieldMetadataId] =
|
||||
useRecoilComponentStateV2(viewPickerKanbanFieldMetadataIdComponentState);
|
||||
|
||||
const setViewPickerType = useSetRecoilComponentStateV2(
|
||||
viewPickerTypeComponentState,
|
||||
);
|
||||
|
||||
const viewPickerReferenceViewId = useRecoilComponentValueV2(
|
||||
viewPickerReferenceViewIdComponentState,
|
||||
);
|
||||
|
||||
const viewPickerIsDirty = useRecoilComponentValueV2(
|
||||
viewPickerIsDirtyComponentState,
|
||||
);
|
||||
|
||||
const viewPickerIsPersisting = useRecoilComponentValueV2(
|
||||
viewPickerIsPersistingComponentState,
|
||||
);
|
||||
|
||||
const { viewsOnCurrentObject } = useGetCurrentView();
|
||||
const referenceView = viewsOnCurrentObject.find(
|
||||
(view) => view.id === viewPickerReferenceViewId,
|
||||
);
|
||||
|
||||
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isDefined(referenceView) &&
|
||||
!viewPickerIsPersisting &&
|
||||
!viewPickerIsDirty
|
||||
) {
|
||||
setViewPickerSelectedIcon(referenceView.icon);
|
||||
setViewPickerInputName(referenceView.name);
|
||||
setViewPickerType(referenceView.type);
|
||||
}
|
||||
}, [
|
||||
referenceView,
|
||||
setViewPickerInputName,
|
||||
setViewPickerSelectedIcon,
|
||||
setViewPickerType,
|
||||
viewPickerIsPersisting,
|
||||
viewPickerIsDirty,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isDefined(referenceView) &&
|
||||
availableFieldsForKanban.length > 0 &&
|
||||
viewPickerKanbanFieldMetadataId === ''
|
||||
) {
|
||||
setViewPickerKanbanFieldMetadataId(
|
||||
referenceView.kanbanFieldMetadataId !== ''
|
||||
? referenceView.kanbanFieldMetadataId
|
||||
: availableFieldsForKanban[0].id,
|
||||
);
|
||||
}
|
||||
}, [
|
||||
referenceView,
|
||||
availableFieldsForKanban,
|
||||
viewPickerKanbanFieldMetadataId,
|
||||
setViewPickerKanbanFieldMetadataId,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,86 @@
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useCreateViewFromCurrentState } from '@/views/view-picker/hooks/useCreateViewFromCurrentState';
|
||||
import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
|
||||
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
|
||||
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
|
||||
import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState';
|
||||
|
||||
export const ViewPickerCreateButton = () => {
|
||||
const { availableFieldsForKanban, navigateToSelectSettings } =
|
||||
useGetAvailableFieldsForKanban();
|
||||
|
||||
const { viewPickerMode } = useViewPickerMode();
|
||||
const viewPickerType = useRecoilComponentValueV2(
|
||||
viewPickerTypeComponentState,
|
||||
);
|
||||
const viewPickerIsPersisting = useRecoilComponentValueV2(
|
||||
viewPickerIsPersistingComponentState,
|
||||
);
|
||||
const viewPickerKanbanFieldMetadataId = useRecoilComponentValueV2(
|
||||
viewPickerKanbanFieldMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const { createViewFromCurrentState } = useCreateViewFromCurrentState();
|
||||
const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
|
||||
|
||||
const handleCreateButtonClick = () => {
|
||||
createViewFromCurrentState();
|
||||
};
|
||||
|
||||
if (viewPickerMode === 'edit') {
|
||||
return (
|
||||
<Button
|
||||
title="Delete"
|
||||
onClick={deleteViewFromCurrentState}
|
||||
accent="danger"
|
||||
fullWidth
|
||||
size="small"
|
||||
justify="center"
|
||||
focus={false}
|
||||
variant="secondary"
|
||||
disabled={viewPickerIsPersisting}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
viewPickerType === ViewType.Kanban &&
|
||||
availableFieldsForKanban.length === 0
|
||||
) {
|
||||
return (
|
||||
<Button
|
||||
title="Go to Settings"
|
||||
onClick={navigateToSelectSettings}
|
||||
size="small"
|
||||
accent="blue"
|
||||
fullWidth
|
||||
justify="center"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
viewPickerType === ViewType.Table ||
|
||||
viewPickerKanbanFieldMetadataId !== ''
|
||||
) {
|
||||
return (
|
||||
<Button
|
||||
title="Create"
|
||||
onClick={handleCreateButtonClick}
|
||||
accent="blue"
|
||||
fullWidth
|
||||
size="small"
|
||||
justify="center"
|
||||
disabled={
|
||||
viewPickerIsPersisting ||
|
||||
(viewPickerType === ViewType.Kanban &&
|
||||
viewPickerKanbanFieldMetadataId === '')
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,83 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
|
||||
import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const ViewPickerCreateOrEditContentEffect = () => {
|
||||
const {
|
||||
viewPickerSelectedIconState,
|
||||
viewPickerInputNameState,
|
||||
viewPickerReferenceViewIdState,
|
||||
viewPickerIsPersistingState,
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
viewPickerTypeState,
|
||||
viewPickerIsDirtyState,
|
||||
} = useViewPickerStates();
|
||||
|
||||
const setViewPickerSelectedIcon = useSetRecoilState(
|
||||
viewPickerSelectedIconState,
|
||||
);
|
||||
const setViewPickerInputName = useSetRecoilState(viewPickerInputNameState);
|
||||
|
||||
const [viewPickerKanbanFieldMetadataId, setViewPickerKanbanFieldMetadataId] =
|
||||
useRecoilState(viewPickerKanbanFieldMetadataIdState);
|
||||
const setViewPickerType = useSetRecoilState(viewPickerTypeState);
|
||||
|
||||
const viewPickerReferenceViewId = useRecoilValue(
|
||||
viewPickerReferenceViewIdState,
|
||||
);
|
||||
|
||||
const viewPickerIsDirty = useRecoilValue(viewPickerIsDirtyState);
|
||||
|
||||
const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState);
|
||||
|
||||
const { viewsOnCurrentObject } = useGetCurrentView();
|
||||
const referenceView = viewsOnCurrentObject.find(
|
||||
(view) => view.id === viewPickerReferenceViewId,
|
||||
);
|
||||
|
||||
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isDefined(referenceView) &&
|
||||
!viewPickerIsPersisting &&
|
||||
!viewPickerIsDirty
|
||||
) {
|
||||
setViewPickerSelectedIcon(referenceView.icon);
|
||||
setViewPickerInputName(referenceView.name);
|
||||
setViewPickerType(referenceView.type);
|
||||
}
|
||||
}, [
|
||||
referenceView,
|
||||
setViewPickerInputName,
|
||||
setViewPickerSelectedIcon,
|
||||
setViewPickerType,
|
||||
viewPickerIsPersisting,
|
||||
viewPickerIsDirty,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isDefined(referenceView) &&
|
||||
availableFieldsForKanban.length > 0 &&
|
||||
viewPickerKanbanFieldMetadataId === ''
|
||||
) {
|
||||
setViewPickerKanbanFieldMetadataId(
|
||||
referenceView.kanbanFieldMetadataId !== ''
|
||||
? referenceView.kanbanFieldMetadataId
|
||||
: availableFieldsForKanban[0].id,
|
||||
);
|
||||
}
|
||||
}, [
|
||||
referenceView,
|
||||
availableFieldsForKanban,
|
||||
viewPickerKanbanFieldMetadataId,
|
||||
setViewPickerKanbanFieldMetadataId,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconList,
|
||||
@ -11,18 +10,19 @@ import {
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
|
||||
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
|
||||
import { ViewPickerCreateOrEditContent } from '@/views/view-picker/components/ViewPickerCreateOrEditContent';
|
||||
import { ViewPickerCreateOrEditContentEffect } from '@/views/view-picker/components/ViewPickerCreateOrEditContentEffect';
|
||||
import { ViewPickerContentCreateMode } from '@/views/view-picker/components/ViewPickerContentCreateMode';
|
||||
import { ViewPickerContentEditMode } from '@/views/view-picker/components/ViewPickerContentEditMode';
|
||||
import { ViewPickerContentEffect } from '@/views/view-picker/components/ViewPickerContentEffect';
|
||||
import { ViewPickerListContent } from '@/views/view-picker/components/ViewPickerListContent';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
import { useUpdateViewFromCurrentState } from '@/views/view-picker/hooks/useUpdateViewFromCurrentState';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { useViewPickerPersistView } from '@/views/view-picker/hooks/useViewPickerPersistView';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { useViewStates } from '../../hooks/internal/useViewStates';
|
||||
|
||||
const StyledDropdownLabelAdornments = styled.span`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.grayScale.gray35};
|
||||
@ -50,14 +50,12 @@ const StyledViewName = styled.span`
|
||||
export const ViewPickerDropdown = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { entityCountInCurrentViewState } = useViewStates();
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const { handleUpdate } = useViewPickerPersistView();
|
||||
const { updateViewFromCurrentState } = useUpdateViewFromCurrentState();
|
||||
|
||||
const entityCountInCurrentView = useRecoilValue(
|
||||
entityCountInCurrentViewState,
|
||||
const entityCountInCurrentView = useRecoilComponentValueV2(
|
||||
entityCountInCurrentViewComponentState,
|
||||
);
|
||||
|
||||
const { isDropdownOpen: isViewsListDropdownOpen } = useDropdown(
|
||||
@ -71,7 +69,7 @@ export const ViewPickerDropdown = () => {
|
||||
|
||||
const handleClickOutside = async () => {
|
||||
if (isViewsListDropdownOpen && viewPickerMode === 'edit') {
|
||||
await handleUpdate();
|
||||
await updateViewFromCurrentState();
|
||||
}
|
||||
setViewPickerMode('list');
|
||||
};
|
||||
@ -106,8 +104,13 @@ export const ViewPickerDropdown = () => {
|
||||
<ViewPickerListContent />
|
||||
) : (
|
||||
<>
|
||||
<ViewPickerCreateOrEditContent />
|
||||
<ViewPickerCreateOrEditContentEffect />
|
||||
{viewPickerMode === 'create-empty' ||
|
||||
viewPickerMode === 'create-from-current' ? (
|
||||
<ViewPickerContentCreateMode />
|
||||
) : (
|
||||
<ViewPickerContentEditMode />
|
||||
)}
|
||||
<ViewPickerContentEffect />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,36 +1,37 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useCreateViewFromCurrentState } from '@/views/view-picker/hooks/useCreateViewFromCurrentState';
|
||||
import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
|
||||
import { useGetAvailableFieldsForKanban } from '@/views/view-picker/hooks/useGetAvailableFieldsForKanban';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { useViewPickerPersistView } from '@/views/view-picker/hooks/useViewPickerPersistView';
|
||||
import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates';
|
||||
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
|
||||
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
|
||||
import { viewPickerTypeComponentState } from '@/views/view-picker/states/viewPickerTypeComponentState';
|
||||
|
||||
export const ViewPickerCreateOrEditButton = () => {
|
||||
export const ViewPickerEditButton = () => {
|
||||
const { availableFieldsForKanban, navigateToSelectSettings } =
|
||||
useGetAvailableFieldsForKanban();
|
||||
|
||||
const {
|
||||
viewPickerIsPersistingState,
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
viewPickerTypeState,
|
||||
} = useViewPickerStates();
|
||||
|
||||
const { viewPickerMode } = useViewPickerMode();
|
||||
const viewPickerType = useRecoilValue(viewPickerTypeState);
|
||||
const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState);
|
||||
const viewPickerKanbanFieldMetadataId = useRecoilValue(
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
const viewPickerType = useRecoilComponentValueV2(
|
||||
viewPickerTypeComponentState,
|
||||
);
|
||||
const viewPickerIsPersisting = useRecoilComponentValueV2(
|
||||
viewPickerIsPersistingComponentState,
|
||||
);
|
||||
const viewPickerKanbanFieldMetadataId = useRecoilComponentValueV2(
|
||||
viewPickerKanbanFieldMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const { handleCreate, handleDelete } = useViewPickerPersistView();
|
||||
const { createViewFromCurrentState } = useCreateViewFromCurrentState();
|
||||
const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
|
||||
|
||||
if (viewPickerMode === 'edit') {
|
||||
return (
|
||||
<Button
|
||||
title="Delete"
|
||||
onClick={handleDelete}
|
||||
onClick={deleteViewFromCurrentState}
|
||||
accent="danger"
|
||||
fullWidth
|
||||
size="small"
|
||||
@ -65,7 +66,7 @@ export const ViewPickerCreateOrEditButton = () => {
|
||||
return (
|
||||
<Button
|
||||
title="Create"
|
||||
onClick={handleCreate}
|
||||
onClick={createViewFromCurrentState}
|
||||
accent="blue"
|
||||
fullWidth
|
||||
size="small"
|
||||
@ -0,0 +1,10 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledIconAndNameContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export { StyledIconAndNameContainer as ViewPickerIconAndNameContainer };
|
||||
@ -1,7 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { DropResult } from '@hello-pangea/dnd';
|
||||
import { MouseEvent, useCallback } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { IconLock, IconPencil, IconPlus, useIcons } from 'twenty-ui';
|
||||
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
@ -11,11 +10,13 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemDraggable } from '@/ui/navigation/menu-item/components/MenuItemDraggable';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useChangeView } from '@/views/hooks/useChangeView';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useHandleViews } from '@/views/hooks/useHandleViews';
|
||||
import { useUpdateView } from '@/views/hooks/useUpdateView';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates';
|
||||
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -24,29 +25,27 @@ const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
||||
`;
|
||||
|
||||
export const ViewPickerListContent = () => {
|
||||
const { selectView } = useHandleViews();
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts, viewsOnCurrentObject } =
|
||||
useGetCurrentView();
|
||||
const { viewPickerReferenceViewIdState } = useViewPickerStates();
|
||||
const setViewPickerReferenceViewId = useSetRecoilState(
|
||||
viewPickerReferenceViewIdState,
|
||||
const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
|
||||
viewPickerReferenceViewIdComponentState,
|
||||
);
|
||||
|
||||
const { setViewPickerMode } = useViewPickerMode();
|
||||
|
||||
const { closeDropdown } = useDropdown(VIEW_PICKER_DROPDOWN_ID);
|
||||
const { updateView } = useHandleViews();
|
||||
const { updateView } = useUpdateView();
|
||||
const { changeView } = useChangeView();
|
||||
|
||||
const handleViewSelect = (viewId: string) => {
|
||||
selectView(viewId);
|
||||
changeView(viewId);
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleAddViewButtonClick = () => {
|
||||
if (isDefined(currentViewWithCombinedFiltersAndSorts?.id)) {
|
||||
setViewPickerReferenceViewId(currentViewWithCombinedFiltersAndSorts.id);
|
||||
setViewPickerMode('create');
|
||||
setViewPickerMode('create-empty');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledSaveButtonContainer = styled.div`
|
||||
display: flex;
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
`;
|
||||
|
||||
export { StyledSaveButtonContainer as ViewPickerSaveButtonContainer };
|
||||
@ -0,0 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledSelectContainer = styled.div`
|
||||
display: flex;
|
||||
width: calc(100% - ${({ theme }) => theme.spacing(2)});
|
||||
margin: ${({ theme }) => theme.spacing(1)};
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export { StyledSelectContainer as ViewPickerSelectContainer };
|
||||
Reference in New Issue
Block a user