New view picker (#4610)
* Implement new view picker * Complete feature * Fixes according to review
This commit is contained in:
@ -1,33 +1,24 @@
|
||||
import { useEffect } from 'react';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { useResetCurrentView } from '@/views/hooks/useResetCurrentView';
|
||||
|
||||
export const FilterQueryParamsEffect = () => {
|
||||
const { hasFiltersQueryParams, getFiltersFromQueryParams, viewIdQueryParam } =
|
||||
export const QueryParamsFiltersEffect = () => {
|
||||
const { hasFiltersQueryParams, getFiltersFromQueryParams } =
|
||||
useViewFromQueryParams();
|
||||
const { unsavedToUpsertViewFiltersState, currentViewIdState } =
|
||||
useViewStates();
|
||||
const { unsavedToUpsertViewFiltersState } = useViewStates();
|
||||
const setUnsavedViewFilter = useSetRecoilState(
|
||||
unsavedToUpsertViewFiltersState,
|
||||
);
|
||||
const setCurrentViewId = useSetRecoilState(currentViewIdState);
|
||||
const { resetCurrentView } = useResetCurrentView();
|
||||
|
||||
useEffect(() => {
|
||||
if (isUndefined(viewIdQueryParam) || !viewIdQueryParam) {
|
||||
if (!hasFiltersQueryParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentViewId(viewIdQueryParam);
|
||||
}, [getFiltersFromQueryParams, setCurrentViewId, viewIdQueryParam]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasFiltersQueryParams) return;
|
||||
|
||||
getFiltersFromQueryParams().then((filtersFromParams) => {
|
||||
if (Array.isArray(filtersFromParams)) {
|
||||
setUnsavedViewFilter(filtersFromParams);
|
||||
@ -44,5 +35,5 @@ export const FilterQueryParamsEffect = () => {
|
||||
setUnsavedViewFilter,
|
||||
]);
|
||||
|
||||
return null;
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,38 @@
|
||||
import { useEffect } from 'react';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useViewFromQueryParams } from '@/views/hooks/internal/useViewFromQueryParams';
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const QueryParamsViewIdEffect = () => {
|
||||
const { getFiltersFromQueryParams, viewIdQueryParam } =
|
||||
useViewFromQueryParams();
|
||||
const { currentViewIdState } = useViewStates();
|
||||
|
||||
const [currentViewId, setCurrentViewId] = useRecoilState(currentViewIdState);
|
||||
const { viewsOnCurrentObject } = useGetCurrentView();
|
||||
|
||||
useEffect(() => {
|
||||
const indexView = viewsOnCurrentObject.find((view) => view.key === 'INDEX');
|
||||
|
||||
if (isUndefined(viewIdQueryParam) && isDefined(indexView)) {
|
||||
setCurrentViewId(indexView.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDefined(viewIdQueryParam)) {
|
||||
setCurrentViewId(viewIdQueryParam);
|
||||
}
|
||||
}, [
|
||||
currentViewId,
|
||||
getFiltersFromQueryParams,
|
||||
setCurrentViewId,
|
||||
viewIdQueryParam,
|
||||
viewsOnCurrentObject,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -7,14 +7,18 @@ import { Button } from '@/ui/input/button/components/Button';
|
||||
import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { UPDATE_VIEW_DROPDOWN_ID } from '@/views/constants/UpdateViewDropdownId';
|
||||
import { UPDATE_VIEW_BUTTON_DROPDOWN_ID } from '@/views/constants/UpdateViewButtonDropdownId';
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
|
||||
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';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
background: ${({ theme }) => theme.color.blue};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
display: inline-flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
@ -23,25 +27,50 @@ const StyledContainer = styled.div`
|
||||
|
||||
export type UpdateViewButtonGroupProps = {
|
||||
hotkeyScope: HotkeyScope;
|
||||
onViewEditModeChange?: () => void;
|
||||
};
|
||||
|
||||
export const UpdateViewButtonGroup = ({
|
||||
hotkeyScope,
|
||||
onViewEditModeChange,
|
||||
}: UpdateViewButtonGroupProps) => {
|
||||
const { canPersistViewSelector, viewEditModeState } = useViewStates();
|
||||
const { canPersistViewSelector, currentViewIdState } = useViewStates();
|
||||
const { saveCurrentViewFilterAndSorts } = useSaveCurrentViewFiltersAndSorts();
|
||||
|
||||
const setViewEditMode = useSetRecoilState(viewEditModeState);
|
||||
const { setViewPickerMode } = useViewPickerMode();
|
||||
const { viewPickerReferenceViewIdState } = useViewPickerStates();
|
||||
const canPersistView = useRecoilValue(canPersistViewSelector());
|
||||
|
||||
const handleCreateViewButtonClick = useCallback(() => {
|
||||
setViewEditMode('create');
|
||||
onViewEditModeChange?.();
|
||||
}, [setViewEditMode, onViewEditModeChange]);
|
||||
const { closeDropdown: closeUpdateViewButtonDropdown } = useDropdown(
|
||||
UPDATE_VIEW_BUTTON_DROPDOWN_ID,
|
||||
);
|
||||
const { openDropdown: openViewPickerDropdown } = useDropdown(
|
||||
VIEW_PICKER_DROPDOWN_ID,
|
||||
);
|
||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const handleViewSubmit = async () => {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
|
||||
const setViewPickerReferenceViewId = useSetRecoilState(
|
||||
viewPickerReferenceViewIdState,
|
||||
);
|
||||
|
||||
const handleViewCreate = useCallback(() => {
|
||||
if (!currentViewId) {
|
||||
return;
|
||||
}
|
||||
openViewPickerDropdown();
|
||||
setViewPickerReferenceViewId(currentViewId);
|
||||
setViewPickerMode('create');
|
||||
|
||||
closeUpdateViewButtonDropdown();
|
||||
}, [
|
||||
closeUpdateViewButtonDropdown,
|
||||
currentViewId,
|
||||
openViewPickerDropdown,
|
||||
setViewPickerMode,
|
||||
setViewPickerReferenceViewId,
|
||||
]);
|
||||
|
||||
const handleViewUpdate = async () => {
|
||||
await saveCurrentViewFilterAndSorts();
|
||||
};
|
||||
|
||||
@ -51,27 +80,42 @@ export const UpdateViewButtonGroup = ({
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ButtonGroup size="small" accent="blue">
|
||||
<Button title="Update view" onClick={handleViewSubmit} />
|
||||
<Dropdown
|
||||
dropdownId={UPDATE_VIEW_DROPDOWN_ID}
|
||||
dropdownHotkeyScope={hotkeyScope}
|
||||
clickableComponent={
|
||||
<Button size="small" accent="blue" Icon={IconChevronDown} />
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleCreateViewButtonClick}
|
||||
LeftIcon={IconPlus}
|
||||
text="Create view"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
}
|
||||
{currentViewWithCombinedFiltersAndSorts?.key !== 'INDEX' ? (
|
||||
<ButtonGroup size="small" accent="blue">
|
||||
<Button title="Update view" onClick={handleViewUpdate} />
|
||||
<Dropdown
|
||||
dropdownId={UPDATE_VIEW_BUTTON_DROPDOWN_ID}
|
||||
dropdownHotkeyScope={hotkeyScope}
|
||||
clickableComponent={
|
||||
<Button
|
||||
size="small"
|
||||
accent="blue"
|
||||
Icon={IconChevronDown}
|
||||
position="right"
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleViewCreate}
|
||||
LeftIcon={IconPlus}
|
||||
text="Create view"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
) : (
|
||||
<Button
|
||||
title="Save as new view"
|
||||
onClick={handleViewCreate}
|
||||
accent="blue"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,26 +4,25 @@ import { useParams } from 'react-router-dom';
|
||||
import { ObjectFilterDropdownButton } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownButton';
|
||||
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
|
||||
import { ObjectSortDropdownButton } from '@/object-record/object-sort-dropdown/components/ObjectSortDropdownButton';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { TopBar } from '@/ui/layout/top-bar/TopBar';
|
||||
import { FilterQueryParamsEffect } from '@/views/components/FilterQueryParamsEffect';
|
||||
import { QueryParamsFiltersEffect } from '@/views/components/QueryParamsFiltersEffect';
|
||||
import { QueryParamsViewIdEffect } from '@/views/components/QueryParamsViewIdEffect';
|
||||
import { ViewBarEffect } from '@/views/components/ViewBarEffect';
|
||||
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
||||
import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect';
|
||||
import { ViewScope } from '@/views/scopes/ViewScope';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { ViewPickerDropdown } from '@/views/view-picker/components/ViewPickerDropdown';
|
||||
|
||||
import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
|
||||
|
||||
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
|
||||
import { ViewBarDetails } from './ViewBarDetails';
|
||||
import { ViewsDropdownButton } from './ViewsDropdownButton';
|
||||
|
||||
export type ViewBarProps = {
|
||||
viewBarId: string;
|
||||
className?: string;
|
||||
optionsDropdownButton: ReactNode;
|
||||
optionsDropdownScopeId: string;
|
||||
onCurrentViewChange: (view: GraphQLView | undefined) => void | Promise<void>;
|
||||
};
|
||||
|
||||
@ -31,18 +30,17 @@ export const ViewBar = ({
|
||||
viewBarId,
|
||||
className,
|
||||
optionsDropdownButton,
|
||||
optionsDropdownScopeId,
|
||||
onCurrentViewChange,
|
||||
}: ViewBarProps) => {
|
||||
const { openDropdown: openOptionsDropdownButton } = useDropdown(
|
||||
optionsDropdownScopeId,
|
||||
);
|
||||
|
||||
const { objectNamePlural } = useParams();
|
||||
|
||||
const filterDropdownId = 'view-filter';
|
||||
const sortDropdownId = 'view-sort';
|
||||
|
||||
if (!objectNamePlural) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewScope
|
||||
viewScopeId={viewBarId}
|
||||
@ -51,16 +49,15 @@ export const ViewBar = ({
|
||||
<ViewBarEffect viewBarId={viewBarId} />
|
||||
<ViewBarFilterEffect filterDropdownId={filterDropdownId} />
|
||||
<ViewBarSortEffect sortDropdownId={sortDropdownId} />
|
||||
{!!objectNamePlural && <FilterQueryParamsEffect />}
|
||||
<QueryParamsFiltersEffect />
|
||||
<QueryParamsViewIdEffect />
|
||||
|
||||
<TopBar
|
||||
className={className}
|
||||
leftComponent={
|
||||
<ViewsDropdownButton
|
||||
onViewEditModeChange={openOptionsDropdownButton}
|
||||
hotkeyScope={{ scope: ViewsHotkeyScope.ListDropdown }}
|
||||
optionsDropdownScopeId={optionsDropdownScopeId}
|
||||
/>
|
||||
<>
|
||||
<ViewPickerDropdown />
|
||||
</>
|
||||
}
|
||||
displayBottomBorder={false}
|
||||
rightComponent={
|
||||
@ -87,8 +84,9 @@ export const ViewBar = ({
|
||||
viewBarId={viewBarId}
|
||||
rightComponent={
|
||||
<UpdateViewButtonGroup
|
||||
onViewEditModeChange={openOptionsDropdownButton}
|
||||
hotkeyScope={{ scope: ViewsHotkeyScope.CreateDropdown }}
|
||||
hotkeyScope={{
|
||||
scope: ViewsHotkeyScope.UpdateViewButtonDropdown,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
type ViewBarEffectProps = {
|
||||
viewBarId: string;
|
||||
@ -17,7 +16,6 @@ export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => {
|
||||
useGetCurrentView(viewBarId);
|
||||
const {
|
||||
onCurrentViewChangeState,
|
||||
currentViewIdState,
|
||||
availableFilterDefinitionsState,
|
||||
isPersistingViewFieldsState,
|
||||
} = useViewStates(viewBarId);
|
||||
@ -30,7 +28,6 @@ export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => {
|
||||
availableFilterDefinitionsState,
|
||||
);
|
||||
const isPersistingViewFields = useRecoilValue(isPersistingViewFieldsState);
|
||||
const [currentViewId, setCurrentViewId] = useRecoilState(currentViewIdState);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -39,14 +36,14 @@ export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => {
|
||||
currentViewSnapshot,
|
||||
)
|
||||
) {
|
||||
setCurrentViewSnapshot(currentViewWithCombinedFiltersAndSorts);
|
||||
|
||||
if (isUndefined(currentViewWithCombinedFiltersAndSorts)) {
|
||||
setCurrentViewSnapshot(currentViewWithCombinedFiltersAndSorts);
|
||||
onCurrentViewChange?.(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPersistingViewFields) {
|
||||
setCurrentViewSnapshot(currentViewWithCombinedFiltersAndSorts);
|
||||
onCurrentViewChange?.(currentViewWithCombinedFiltersAndSorts);
|
||||
}
|
||||
}
|
||||
@ -58,14 +55,5 @@ export const ViewBarEffect = ({ viewBarId }: ViewBarEffectProps) => {
|
||||
onCurrentViewChange,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isDefined(currentViewWithCombinedFiltersAndSorts) &&
|
||||
!isDefined(currentViewId)
|
||||
) {
|
||||
setCurrentViewId(currentViewWithCombinedFiltersAndSorts.id);
|
||||
}
|
||||
}, [currentViewWithCombinedFiltersAndSorts, currentViewId, setCurrentViewId]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -1,189 +0,0 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconList,
|
||||
IconPencil,
|
||||
IconPlus,
|
||||
IconTrash,
|
||||
} from '@/ui/display/icon';
|
||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { StyledDropdownButtonContainer } from '@/ui/layout/dropdown/components/StyledDropdownButtonContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { VIEWS_DROPDOWN_ID } from '@/views/constants/ViewsDropdownId';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useHandleViews } from '@/views/hooks/useHandleViews';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { useViewStates } from '../hooks/internal/useViewStates';
|
||||
|
||||
const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
`;
|
||||
|
||||
const StyledDropdownLabelAdornments = styled.span`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.grayScale.gray35};
|
||||
display: inline-flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledViewName = styled.span`
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
display: inline-block;
|
||||
max-width: 130px;
|
||||
@media (max-width: 375px) {
|
||||
max-width: 90px;
|
||||
}
|
||||
@media (min-width: 376px) and (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
max-width: 110px;
|
||||
}
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export type ViewsDropdownButtonProps = {
|
||||
hotkeyScope: HotkeyScope;
|
||||
onViewEditModeChange?: () => void;
|
||||
optionsDropdownScopeId: string;
|
||||
};
|
||||
|
||||
export const ViewsDropdownButton = ({
|
||||
hotkeyScope,
|
||||
onViewEditModeChange,
|
||||
optionsDropdownScopeId,
|
||||
}: ViewsDropdownButtonProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { removeView, selectView } = useHandleViews();
|
||||
const { entityCountInCurrentViewState, viewEditModeState } = useViewStates();
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts, viewsOnCurrentObject } =
|
||||
useGetCurrentView();
|
||||
|
||||
const entityCountInCurrentView = useRecoilValue(
|
||||
entityCountInCurrentViewState,
|
||||
);
|
||||
|
||||
const setViewEditMode = useSetRecoilState(viewEditModeState);
|
||||
|
||||
const {
|
||||
isDropdownOpen: isViewsDropdownOpen,
|
||||
closeDropdown: closeViewsDropdown,
|
||||
} = useDropdown(VIEWS_DROPDOWN_ID);
|
||||
|
||||
const { openDropdown: openOptionsDropdown } = useDropdown(
|
||||
optionsDropdownScopeId,
|
||||
);
|
||||
|
||||
const handleViewSelect = (viewId: string) => {
|
||||
selectView(viewId);
|
||||
closeViewsDropdown();
|
||||
};
|
||||
|
||||
const handleAddViewButtonClick = () => {
|
||||
setViewEditMode('create');
|
||||
onViewEditModeChange?.();
|
||||
closeViewsDropdown();
|
||||
openOptionsDropdown();
|
||||
};
|
||||
|
||||
const handleEditViewButtonClick = (
|
||||
event: MouseEvent<HTMLButtonElement>,
|
||||
viewId: string,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
selectView(viewId);
|
||||
setViewEditMode('edit');
|
||||
onViewEditModeChange?.();
|
||||
closeViewsDropdown();
|
||||
openOptionsDropdown();
|
||||
};
|
||||
|
||||
const handleDeleteViewButtonClick = async (
|
||||
event: MouseEvent<HTMLButtonElement>,
|
||||
viewId: string,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
|
||||
await removeView(viewId);
|
||||
selectView(viewsOnCurrentObject.filter((view) => view.id !== viewId)[0].id);
|
||||
closeViewsDropdown();
|
||||
};
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const CurrentViewIcon = getIcon(currentViewWithCombinedFiltersAndSorts?.icon);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={VIEWS_DROPDOWN_ID}
|
||||
dropdownHotkeyScope={hotkeyScope}
|
||||
clickableComponent={
|
||||
<StyledDropdownButtonContainer isUnfolded={isViewsDropdownOpen}>
|
||||
{currentViewWithCombinedFiltersAndSorts && CurrentViewIcon ? (
|
||||
<CurrentViewIcon size={theme.icon.size.md} />
|
||||
) : (
|
||||
<IconList size={theme.icon.size.md} />
|
||||
)}
|
||||
<StyledViewName>
|
||||
{currentViewWithCombinedFiltersAndSorts?.name ?? 'All'}
|
||||
</StyledViewName>
|
||||
<StyledDropdownLabelAdornments>
|
||||
· {entityCountInCurrentView}{' '}
|
||||
<IconChevronDown size={theme.icon.size.sm} />
|
||||
</StyledDropdownLabelAdornments>
|
||||
</StyledDropdownButtonContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
{viewsOnCurrentObject.map((view) => (
|
||||
<MenuItem
|
||||
key={view.id}
|
||||
iconButtons={[
|
||||
currentViewWithCombinedFiltersAndSorts?.id === view.id
|
||||
? {
|
||||
Icon: IconPencil,
|
||||
onClick: (event: MouseEvent<HTMLButtonElement>) =>
|
||||
handleEditViewButtonClick(event, view.id),
|
||||
}
|
||||
: null,
|
||||
viewsOnCurrentObject.length > 1
|
||||
? {
|
||||
Icon: IconTrash,
|
||||
onClick: (event: MouseEvent<HTMLButtonElement>) =>
|
||||
handleDeleteViewButtonClick(event, view.id),
|
||||
}
|
||||
: null,
|
||||
].filter(isDefined)}
|
||||
onClick={() => handleViewSelect(view.id)}
|
||||
LeftIcon={getIcon(view.icon)}
|
||||
text={view.name}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<StyledBoldDropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleAddViewButtonClick}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add view"
|
||||
/>
|
||||
</StyledBoldDropdownMenuItemsContainer>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export const UPDATE_VIEW_BUTTON_DROPDOWN_ID = 'update-view-button';
|
||||
@ -1 +0,0 @@
|
||||
export const UPDATE_VIEW_DROPDOWN_ID = 'update-view';
|
||||
@ -1 +0,0 @@
|
||||
export const VIEWS_DROPDOWN_ID = 'views';
|
||||
@ -52,7 +52,10 @@ export const useViewFromQueryParams = () => {
|
||||
[queryParamsValidation],
|
||||
);
|
||||
const viewIdQueryParam = useMemo(
|
||||
() => queryParamsValidation.success && queryParamsValidation.data.view,
|
||||
() =>
|
||||
queryParamsValidation.success
|
||||
? queryParamsValidation.data.view
|
||||
: undefined,
|
||||
[queryParamsValidation],
|
||||
);
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@ import { unsavedToDeleteViewFilterIdsComponentState } from '@/views/states/unsav
|
||||
import { unsavedToDeleteViewSortIdsComponentState } from '@/views/states/unsavedToDeleteViewSortIdsComponentState';
|
||||
import { unsavedToUpsertViewFiltersComponentState } from '@/views/states/unsavedToUpsertViewFiltersComponentState';
|
||||
import { unsavedToUpsertViewSortsComponentState } from '@/views/states/unsavedToUpsertViewSortsComponentState';
|
||||
import { viewEditModeComponentState } from '@/views/states/viewEditModeComponentState';
|
||||
import { viewObjectMetadataIdComponentState } from '@/views/states/viewObjectMetadataIdComponentState';
|
||||
|
||||
import { ViewScopeInternalContext } from '../../scopes/scope-internal-context/ViewScopeInternalContext';
|
||||
@ -60,10 +59,6 @@ export const useViewStates = (viewComponentId?: string) => {
|
||||
entityCountInCurrentViewComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewEditModeState: extractComponentState(
|
||||
viewEditModeComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewObjectMetadataIdState: extractComponentState(
|
||||
viewObjectMetadataIdComponentState,
|
||||
componentId,
|
||||
|
||||
@ -9,6 +9,7 @@ import { ViewScopeInternalContext } from '@/views/scopes/scope-internal-context/
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { combinedViewFilters } from '@/views/utils/combinedViewFilters';
|
||||
import { combinedViewSorts } from '@/views/utils/combinedViewSorts';
|
||||
import { getObjectMetadataItemViews } from '@/views/utils/getObjectMetadataItemViews';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useGetCurrentView = (viewBarComponentId?: string) => {
|
||||
@ -51,16 +52,10 @@ export const useGetCurrentView = (viewBarComponentId?: string) => {
|
||||
setIsCurrentViewKeyIndex(currentView?.key === 'INDEX');
|
||||
}, [currentView, setIsCurrentViewKeyIndex]);
|
||||
|
||||
const viewsOnCurrentObject = views
|
||||
.filter((view) => view.objectMetadataId === viewObjectMetadataId)
|
||||
.map((view) => ({
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
type: view.type,
|
||||
key: view.key,
|
||||
objectMetadataId: view.objectMetadataId,
|
||||
icon: view.icon,
|
||||
}));
|
||||
const viewsOnCurrentObject = getObjectMetadataItemViews(
|
||||
viewObjectMetadataId ?? '',
|
||||
views,
|
||||
);
|
||||
|
||||
const unsavedToUpsertViewFilters = useRecoilValue(
|
||||
unsavedToUpsertViewFiltersState,
|
||||
|
||||
@ -12,6 +12,7 @@ import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistVie
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { useGetViewFromCache } from '@/views/hooks/useGetViewFromCache';
|
||||
import { useResetCurrentView } from '@/views/hooks/useResetCurrentView';
|
||||
import { useSaveCurrentViewFiltersAndSorts } from '@/views/hooks/useSaveCurrentViewFiltersAndSorts';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
@ -19,7 +20,8 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
export const useHandleViews = (viewBarComponentId?: string) => {
|
||||
const { resetCurrentView } = useResetCurrentView(viewBarComponentId);
|
||||
|
||||
const { currentViewIdState } = useViewStates(viewBarComponentId);
|
||||
const { currentViewIdState, isPersistingViewFieldsState } =
|
||||
useViewStates(viewBarComponentId);
|
||||
|
||||
const { getViewFromCache } = useGetViewFromCache();
|
||||
|
||||
@ -36,8 +38,8 @@ export const useHandleViews = (viewBarComponentId?: string) => {
|
||||
});
|
||||
|
||||
const { createViewFieldRecords } = usePersistViewFieldRecords();
|
||||
|
||||
const createViewFromCurrent = useRecoilCallback(() => () => {}, []);
|
||||
const { saveCurrentViewFilterAndSorts } =
|
||||
useSaveCurrentViewFiltersAndSorts(viewBarComponentId);
|
||||
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
|
||||
@ -48,9 +50,20 @@ export const useHandleViews = (viewBarComponentId?: string) => {
|
||||
[deleteOneRecord],
|
||||
);
|
||||
|
||||
const createEmptyView = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async (id: string, name: string) => {
|
||||
const createView = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async ({
|
||||
id,
|
||||
name,
|
||||
icon,
|
||||
kanbanFieldMetadataId,
|
||||
type,
|
||||
}: Partial<
|
||||
Pick<
|
||||
GraphQLView,
|
||||
'id' | 'name' | 'icon' | 'kanbanFieldMetadataId' | 'type'
|
||||
>
|
||||
>) => {
|
||||
const currentViewId = getSnapshotValue(snapshot, currentViewIdState);
|
||||
|
||||
if (!isDefined(currentViewId)) {
|
||||
@ -63,11 +76,17 @@ export const useHandleViews = (viewBarComponentId?: string) => {
|
||||
return;
|
||||
}
|
||||
|
||||
set(isPersistingViewFieldsState, true);
|
||||
|
||||
const newView = await createOneRecord({
|
||||
id: id ?? v4(),
|
||||
name: name,
|
||||
name: name ?? view.name,
|
||||
icon: icon ?? view.icon,
|
||||
key: null,
|
||||
kanbanFieldMetadataId:
|
||||
kanbanFieldMetadataId ?? view.kanbanFieldMetadataId,
|
||||
type: type ?? view.type,
|
||||
objectMetadataId: view.objectMetadataId,
|
||||
type: view.type,
|
||||
});
|
||||
|
||||
if (isUndefinedOrNull(newView)) {
|
||||
@ -75,33 +94,36 @@ export const useHandleViews = (viewBarComponentId?: string) => {
|
||||
}
|
||||
|
||||
await createViewFieldRecords(view.viewFields, newView);
|
||||
await saveCurrentViewFilterAndSorts(newView.id);
|
||||
set(isPersistingViewFieldsState, false);
|
||||
},
|
||||
[
|
||||
createOneRecord,
|
||||
createViewFieldRecords,
|
||||
currentViewIdState,
|
||||
getViewFromCache,
|
||||
isPersistingViewFieldsState,
|
||||
saveCurrentViewFilterAndSorts,
|
||||
],
|
||||
);
|
||||
|
||||
const changeViewInUrl = useCallback(
|
||||
(viewId: string) => {
|
||||
setSearchParams((previousSearchParams) => {
|
||||
previousSearchParams.set('view', viewId);
|
||||
return previousSearchParams;
|
||||
setSearchParams(() => {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('view', viewId);
|
||||
return searchParams;
|
||||
});
|
||||
},
|
||||
[setSearchParams],
|
||||
);
|
||||
|
||||
const selectView = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async (viewId: string) => {
|
||||
set(currentViewIdState, viewId);
|
||||
changeViewInUrl(viewId);
|
||||
resetCurrentView();
|
||||
},
|
||||
[changeViewInUrl, currentViewIdState, resetCurrentView],
|
||||
() => async (viewId: string) => {
|
||||
changeViewInUrl(viewId);
|
||||
resetCurrentView();
|
||||
},
|
||||
[changeViewInUrl, resetCurrentView],
|
||||
);
|
||||
|
||||
const updateCurrentView = useRecoilCallback(
|
||||
@ -120,11 +142,23 @@ export const useHandleViews = (viewBarComponentId?: string) => {
|
||||
[currentViewIdState, updateOneRecord],
|
||||
);
|
||||
|
||||
const updateView = useRecoilCallback(
|
||||
() => async (view: Partial<GraphQLView>) => {
|
||||
if (isDefined(view.id)) {
|
||||
await updateOneRecord({
|
||||
idToUpdate: view.id,
|
||||
updateOneRecordInput: view,
|
||||
});
|
||||
}
|
||||
},
|
||||
[updateOneRecord],
|
||||
);
|
||||
|
||||
return {
|
||||
selectView,
|
||||
updateCurrentView,
|
||||
updateView,
|
||||
removeView,
|
||||
createEmptyView,
|
||||
createViewFromCurrent,
|
||||
createView,
|
||||
};
|
||||
};
|
||||
|
||||
@ -125,7 +125,7 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
|
||||
const saveCurrentViewFilterAndSorts = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
async () => {
|
||||
async (viewId?: string) => {
|
||||
const currentViewId = snapshot
|
||||
.getLoadable(currentViewIdState)
|
||||
.getValue();
|
||||
@ -134,8 +134,8 @@ export const useSaveCurrentViewFiltersAndSorts = (
|
||||
return;
|
||||
}
|
||||
|
||||
await saveViewFilters(currentViewId);
|
||||
await saveViewSorts(currentViewId);
|
||||
await saveViewFilters(viewId ?? currentViewId);
|
||||
await saveViewSorts(viewId ?? currentViewId);
|
||||
resetCurrentView();
|
||||
},
|
||||
[currentViewIdState, resetCurrentView, saveViewFilters, saveViewSorts],
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
|
||||
export const useViewBarEditMode = (viewBarComponentId?: string) => {
|
||||
const { viewEditModeState } = useViewStates(viewBarComponentId);
|
||||
|
||||
const [viewEditMode, setViewEditMode] = useRecoilState(viewEditModeState);
|
||||
|
||||
return {
|
||||
viewEditMode,
|
||||
setViewEditMode,
|
||||
};
|
||||
};
|
||||
@ -1,4 +1,3 @@
|
||||
import { Position } from '@/object-metadata/types/Position';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
@ -10,11 +9,12 @@ export type GraphQLView = {
|
||||
name: string;
|
||||
type: ViewType;
|
||||
key: ViewKey | null;
|
||||
kanbanFieldMetadataId: string;
|
||||
objectMetadataId: string;
|
||||
isCompact: boolean;
|
||||
viewFields: ViewField[];
|
||||
viewFilters: ViewFilter[];
|
||||
viewSorts: ViewSort[];
|
||||
position: Position;
|
||||
position: number;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum ViewsHotkeyScope {
|
||||
ListDropdown = 'views-list-dropdown',
|
||||
CreateDropdown = 'views-create-dropdown',
|
||||
UpdateViewButtonDropdown = 'update-view-button-dropdown',
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
|
||||
export const getObjectMetadataItemViews = (
|
||||
viewObjectMetadataId: string,
|
||||
views: GraphQLView[],
|
||||
) => {
|
||||
const indexView = views.find(
|
||||
(view) =>
|
||||
view.key === 'INDEX' && view.objectMetadataId === viewObjectMetadataId,
|
||||
);
|
||||
|
||||
return [
|
||||
...views
|
||||
.filter((view) => view.objectMetadataId === viewObjectMetadataId)
|
||||
.filter((view) => view.key !== 'INDEX'),
|
||||
]
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.concat(indexView ? [indexView] : [])
|
||||
.map((view) => ({
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
type: view.type,
|
||||
key: view.key,
|
||||
position: view.position,
|
||||
objectMetadataId: view.objectMetadataId,
|
||||
kanbanFieldMetadataId: view.kanbanFieldMetadataId,
|
||||
icon: view.icon,
|
||||
}));
|
||||
};
|
||||
@ -0,0 +1,81 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
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';
|
||||
|
||||
export const ViewPickerCreateOrEditButton = () => {
|
||||
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 { handleCreate, handleDelete } = useViewPickerPersistView();
|
||||
|
||||
if (viewPickerMode === 'edit') {
|
||||
return (
|
||||
<Button
|
||||
title="Delete"
|
||||
onClick={handleDelete}
|
||||
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={handleCreate}
|
||||
accent="blue"
|
||||
fullWidth
|
||||
size="small"
|
||||
justify="center"
|
||||
disabled={
|
||||
viewPickerIsPersisting ||
|
||||
(viewPickerType === ViewType.Kanban &&
|
||||
viewPickerKanbanFieldMetadataId === '')
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,180 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { IconChevronLeft, IconX } from '@/ui/display/icon';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
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 { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { ViewPickerCreateOrEditButton } from '@/views/view-picker/components/ViewPickerCreateOrEditButton';
|
||||
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 { 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;
|
||||
`;
|
||||
|
||||
const StyledNoKanbanFieldAvailableContainer = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: flex;
|
||||
margin: ${({ theme }) => theme.spacing(1, 2)};
|
||||
user-select: none;
|
||||
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,
|
||||
} = useViewPickerStates();
|
||||
|
||||
const [viewPickerInputName, setViewPickerInputName] = useRecoilState(
|
||||
viewPickerInputNameState,
|
||||
);
|
||||
const [viewPickerSelectedIcon, setViewPickerSelectedIcon] = useRecoilState(
|
||||
viewPickerSelectedIconState,
|
||||
);
|
||||
const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState);
|
||||
|
||||
const [viewPickerKanbanFieldMetadataId, setViewPickerKanbanFieldMetadataId] =
|
||||
useRecoilState(viewPickerKanbanFieldMetadataIdState);
|
||||
|
||||
const [viewPickerType, setViewPickerType] =
|
||||
useRecoilState(viewPickerTypeState);
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { handleCreate, handleUpdate } = useViewPickerPersistView();
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Enter,
|
||||
async () => {
|
||||
if (viewPickerMode === 'create') {
|
||||
if (viewPickerIsPersisting) {
|
||||
return;
|
||||
}
|
||||
await handleCreate();
|
||||
}
|
||||
if (viewPickerMode === 'edit') {
|
||||
if (viewPickerIsPersisting) {
|
||||
return;
|
||||
}
|
||||
await handleUpdate();
|
||||
}
|
||||
},
|
||||
ViewsHotkeyScope.ListDropdown,
|
||||
);
|
||||
|
||||
const onIconChange = ({ iconKey }: { iconKey: string }) => {
|
||||
setViewPickerSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader
|
||||
StartIcon={viewPickerMode === 'create' ? IconX : IconChevronLeft}
|
||||
onClick={() => setViewPickerMode('list')}
|
||||
>
|
||||
{viewPickerMode === 'create' ? 'Create view' : 'Edit view'}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledIconAndNameContainer>
|
||||
<IconPicker
|
||||
onChange={onIconChange}
|
||||
selectedIconKey={viewPickerSelectedIcon}
|
||||
disableBlur
|
||||
onClose={() => setHotkeyScope(ViewsHotkeyScope.ListDropdown)}
|
||||
/>
|
||||
<DropdownMenuInput
|
||||
value={viewPickerInputName}
|
||||
onChange={(event) => setViewPickerInputName(event.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</StyledIconAndNameContainer>
|
||||
{viewPickerMode === 'create' && (
|
||||
<StyledSelectContainer>
|
||||
<Select
|
||||
disableBlur
|
||||
label="View type"
|
||||
fullWidth
|
||||
value={viewPickerType}
|
||||
onChange={(value) => setViewPickerType(value)}
|
||||
options={[
|
||||
{ value: ViewType.Table, label: 'Table' },
|
||||
{ value: ViewType.Kanban, label: 'Kanban' },
|
||||
]}
|
||||
dropdownId={VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID}
|
||||
/>
|
||||
</StyledSelectContainer>
|
||||
)}
|
||||
{viewPickerType === ViewType.Kanban && viewPickerMode === 'create' && (
|
||||
<>
|
||||
<StyledSelectContainer>
|
||||
<Select
|
||||
disableBlur
|
||||
label="Stages"
|
||||
fullWidth
|
||||
value={viewPickerKanbanFieldMetadataId}
|
||||
onChange={(value) => setViewPickerKanbanFieldMetadataId(value)}
|
||||
options={
|
||||
availableFieldsForKanban.length > 0
|
||||
? availableFieldsForKanban.map((field) => ({
|
||||
value: field.id,
|
||||
label: field.label,
|
||||
}))
|
||||
: [{ value: '', label: 'No Select field' }]
|
||||
}
|
||||
dropdownId={VIEW_PICKER_KANBAN_FIELD_DROPDOWN_ID}
|
||||
/>
|
||||
</StyledSelectContainer>
|
||||
{availableFieldsForKanban.length === 0 && (
|
||||
<StyledNoKanbanFieldAvailableContainer>
|
||||
Set up a Select field on Companies to create a Kanban
|
||||
</StyledNoKanbanFieldAvailableContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<StyledSaveButtonContainer>
|
||||
<ViewPickerCreateOrEditButton />
|
||||
</StyledSaveButtonContainer>
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
import { useEffect } from 'react';
|
||||
import { 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,
|
||||
} = useViewPickerStates();
|
||||
|
||||
const setViewPickerSelectedIcon = useSetRecoilState(
|
||||
viewPickerSelectedIconState,
|
||||
);
|
||||
const setViewPickerInputName = useSetRecoilState(viewPickerInputNameState);
|
||||
|
||||
const setViewPickerKanbanFieldMetadataId = useSetRecoilState(
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
);
|
||||
const setViewPickerType = useSetRecoilState(viewPickerTypeState);
|
||||
|
||||
const viewPickerReferenceViewId = useRecoilValue(
|
||||
viewPickerReferenceViewIdState,
|
||||
);
|
||||
|
||||
const viewPickerIsPersisting = useRecoilValue(viewPickerIsPersistingState);
|
||||
|
||||
const { viewsOnCurrentObject } = useGetCurrentView();
|
||||
const referenceView = viewsOnCurrentObject.find(
|
||||
(view) => view.id === viewPickerReferenceViewId,
|
||||
);
|
||||
|
||||
const { availableFieldsForKanban } = useGetAvailableFieldsForKanban();
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(referenceView) && !viewPickerIsPersisting) {
|
||||
setViewPickerSelectedIcon(referenceView.icon);
|
||||
setViewPickerInputName(referenceView.name);
|
||||
setViewPickerKanbanFieldMetadataId(referenceView.kanbanFieldMetadataId);
|
||||
setViewPickerType(referenceView.type);
|
||||
}
|
||||
}, [
|
||||
referenceView,
|
||||
setViewPickerInputName,
|
||||
setViewPickerKanbanFieldMetadataId,
|
||||
setViewPickerSelectedIcon,
|
||||
setViewPickerType,
|
||||
viewPickerIsPersisting,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (availableFieldsForKanban.length > 0) {
|
||||
setViewPickerKanbanFieldMetadataId(availableFieldsForKanban[0].id);
|
||||
}
|
||||
}, [availableFieldsForKanban, setViewPickerKanbanFieldMetadataId]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,100 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { IconChevronDown, IconList } from '@/ui/display/icon';
|
||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||
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 { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { ViewsHotkeyScope } from '@/views/types/ViewsHotkeyScope';
|
||||
import { ViewPickerCreateOrEditContent } from '@/views/view-picker/components/ViewPickerCreateOrEditContent';
|
||||
import { ViewPickerCreateOrEditContentEffect } from '@/views/view-picker/components/ViewPickerCreateOrEditContentEffect';
|
||||
import { ViewPickerListContent } from '@/views/view-picker/components/ViewPickerListContent';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
import { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
|
||||
import { useViewStates } from '../../hooks/internal/useViewStates';
|
||||
|
||||
const StyledDropdownLabelAdornments = styled.span`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.grayScale.gray35};
|
||||
display: inline-flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledViewName = styled.span`
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
display: inline-block;
|
||||
max-width: 130px;
|
||||
@media (max-width: 375px) {
|
||||
max-width: 90px;
|
||||
}
|
||||
@media (min-width: 376px) and (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
max-width: 110px;
|
||||
}
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export const ViewPickerDropdown = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { entityCountInCurrentViewState } = useViewStates();
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const entityCountInCurrentView = useRecoilValue(
|
||||
entityCountInCurrentViewState,
|
||||
);
|
||||
|
||||
const { isDropdownOpen: isViewsListDropdownOpen } = useDropdown(
|
||||
VIEW_PICKER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const { viewPickerMode, setViewPickerMode } = useViewPickerMode();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const CurrentViewIcon = getIcon(currentViewWithCombinedFiltersAndSorts?.icon);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={VIEW_PICKER_DROPDOWN_ID}
|
||||
dropdownHotkeyScope={{ scope: ViewsHotkeyScope.ListDropdown }}
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
dropdownMenuWidth={200}
|
||||
onClickOutside={() => setViewPickerMode('list')}
|
||||
clickableComponent={
|
||||
<StyledDropdownButtonContainer isUnfolded={isViewsListDropdownOpen}>
|
||||
{currentViewWithCombinedFiltersAndSorts && CurrentViewIcon ? (
|
||||
<CurrentViewIcon size={theme.icon.size.md} />
|
||||
) : (
|
||||
<IconList size={theme.icon.size.md} />
|
||||
)}
|
||||
<StyledViewName>
|
||||
{currentViewWithCombinedFiltersAndSorts?.name ?? 'All'}
|
||||
</StyledViewName>
|
||||
<StyledDropdownLabelAdornments>
|
||||
· {entityCountInCurrentView}{' '}
|
||||
<IconChevronDown size={theme.icon.size.sm} />
|
||||
</StyledDropdownLabelAdornments>
|
||||
</StyledDropdownButtonContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
viewPickerMode === 'list' ? (
|
||||
<ViewPickerListContent />
|
||||
) : (
|
||||
<>
|
||||
<ViewPickerCreateOrEditContent />
|
||||
<ViewPickerCreateOrEditContentEffect />
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,93 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { IconLock, IconPencil, IconPlus } from '@/ui/display/icon';
|
||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useHandleViews } from '@/views/hooks/useHandleViews';
|
||||
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 { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
`;
|
||||
|
||||
export const ViewPickerListContent = () => {
|
||||
const { selectView } = useHandleViews();
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts, viewsOnCurrentObject } =
|
||||
useGetCurrentView();
|
||||
|
||||
const { viewPickerReferenceViewIdState } = useViewPickerStates();
|
||||
const setViewPickerReferenceViewId = useSetRecoilState(
|
||||
viewPickerReferenceViewIdState,
|
||||
);
|
||||
|
||||
const { setViewPickerMode } = useViewPickerMode();
|
||||
|
||||
const { closeDropdown } = useDropdown(VIEW_PICKER_DROPDOWN_ID);
|
||||
|
||||
const handleViewSelect = (viewId: string) => {
|
||||
selectView(viewId);
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const handleAddViewButtonClick = () => {
|
||||
if (isDefined(currentViewWithCombinedFiltersAndSorts?.id)) {
|
||||
setViewPickerReferenceViewId(currentViewWithCombinedFiltersAndSorts.id);
|
||||
setViewPickerMode('create');
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditViewButtonClick = (
|
||||
event: MouseEvent<HTMLButtonElement>,
|
||||
viewId: string,
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
setViewPickerReferenceViewId(viewId);
|
||||
setViewPickerMode('edit');
|
||||
};
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
{viewsOnCurrentObject.map((view) => (
|
||||
<MenuItem
|
||||
key={view.id}
|
||||
iconButtons={[
|
||||
view.key !== 'INDEX'
|
||||
? {
|
||||
Icon: IconPencil,
|
||||
onClick: (event: MouseEvent<HTMLButtonElement>) =>
|
||||
handleEditViewButtonClick(event, view.id),
|
||||
}
|
||||
: {
|
||||
Icon: IconLock,
|
||||
},
|
||||
].filter(isDefined)}
|
||||
onClick={() => handleViewSelect(view.id)}
|
||||
LeftIcon={getIcon(view.icon)}
|
||||
text={view.name}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<StyledBoldDropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleAddViewButtonClick}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add view"
|
||||
/>
|
||||
</StyledBoldDropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
export const VIEW_PICKER_DROPDOWN_ID = 'view-picker';
|
||||
@ -0,0 +1 @@
|
||||
export const VIEW_PICKER_KANBAN_FIELD_DROPDOWN_ID = 'view-picker-kanban-field';
|
||||
@ -0,0 +1 @@
|
||||
export const VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID = 'view-picker-view-type';
|
||||
@ -0,0 +1,46 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { VIEW_PICKER_DROPDOWN_ID } from '@/views/view-picker/constants/ViewPickerDropdownId';
|
||||
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 { useViewPickerMode } from '@/views/view-picker/hooks/useViewPickerMode';
|
||||
import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates';
|
||||
|
||||
export const useCloseAndResetViewPicker = () => {
|
||||
const { setViewPickerMode } = useViewPickerMode();
|
||||
const { viewPickerIsPersistingState } = useViewPickerStates();
|
||||
|
||||
const setViewPickerIsPersisting = useSetRecoilState(
|
||||
viewPickerIsPersistingState,
|
||||
);
|
||||
|
||||
const { closeDropdown: closeViewPickerDropdown } = useDropdown(
|
||||
VIEW_PICKER_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const { closeDropdown: closeKanbanFieldDropdown } = useDropdown(
|
||||
VIEW_PICKER_KANBAN_FIELD_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const { closeDropdown: closeTypeDropdown } = useDropdown(
|
||||
VIEW_PICKER_VIEW_TYPE_DROPDOWN_ID,
|
||||
);
|
||||
|
||||
const closeAndResetViewPicker = useCallback(() => {
|
||||
setViewPickerIsPersisting(false);
|
||||
setViewPickerMode('list');
|
||||
closeKanbanFieldDropdown();
|
||||
closeTypeDropdown();
|
||||
closeViewPickerDropdown();
|
||||
}, [
|
||||
closeKanbanFieldDropdown,
|
||||
closeTypeDropdown,
|
||||
closeViewPickerDropdown,
|
||||
setViewPickerIsPersisting,
|
||||
setViewPickerMode,
|
||||
]);
|
||||
|
||||
return { closeAndResetViewPicker };
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useGetAvailableFieldsForKanban = () => {
|
||||
const { viewObjectMetadataIdState } = useViewStates();
|
||||
|
||||
const viewObjectMetadataId = useRecoilValue(viewObjectMetadataIdState);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadata) => objectMetadata.id === viewObjectMetadataId,
|
||||
);
|
||||
|
||||
const availableFieldsForKanban =
|
||||
objectMetadataItem?.fields.filter(
|
||||
(field) => field.type === FieldMetadataType.Select,
|
||||
) ?? [];
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const navigateToSelectSettings = useCallback(() => {
|
||||
navigate(`/settings/objects/${objectMetadataItem?.namePlural}`);
|
||||
}, [navigate, objectMetadataItem?.namePlural]);
|
||||
|
||||
return {
|
||||
availableFieldsForKanban,
|
||||
navigateToSelectSettings,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates';
|
||||
|
||||
export const useViewPickerMode = (viewBarComponentId?: string) => {
|
||||
const { viewPickerModeState } = useViewPickerStates(viewBarComponentId);
|
||||
|
||||
const [viewPickerMode, setViewPickerMode] =
|
||||
useRecoilState(viewPickerModeState);
|
||||
|
||||
return {
|
||||
viewPickerMode,
|
||||
setViewPickerMode,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,125 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useHandleViews } from '@/views/hooks/useHandleViews';
|
||||
import { useCloseAndResetViewPicker } from '@/views/view-picker/hooks/useCloseAndResetViewPicker';
|
||||
import { useViewPickerStates } from '@/views/view-picker/hooks/useViewPickerStates';
|
||||
|
||||
export const useViewPickerPersistView = () => {
|
||||
const {
|
||||
viewPickerInputNameState,
|
||||
viewPickerSelectedIconState,
|
||||
viewPickerIsPersistingState,
|
||||
viewPickerReferenceViewIdState,
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
viewPickerTypeState,
|
||||
} = useViewPickerStates();
|
||||
|
||||
const { createView, selectView, removeView, updateView } = useHandleViews();
|
||||
|
||||
const { viewsOnCurrentObject } = useGetCurrentView();
|
||||
|
||||
const { closeAndResetViewPicker } = useCloseAndResetViewPicker();
|
||||
|
||||
const handleCreate = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async () => {
|
||||
const name = getSnapshotValue(snapshot, viewPickerInputNameState);
|
||||
const iconKey = getSnapshotValue(snapshot, viewPickerSelectedIconState);
|
||||
const type = getSnapshotValue(snapshot, viewPickerTypeState);
|
||||
const kanbanFieldMetadataId = getSnapshotValue(
|
||||
snapshot,
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
);
|
||||
const id = v4();
|
||||
set(viewPickerIsPersistingState, true);
|
||||
await createView({
|
||||
id,
|
||||
name,
|
||||
icon: iconKey,
|
||||
type,
|
||||
kanbanFieldMetadataId,
|
||||
});
|
||||
closeAndResetViewPicker();
|
||||
selectView(id);
|
||||
},
|
||||
[
|
||||
closeAndResetViewPicker,
|
||||
createView,
|
||||
selectView,
|
||||
viewPickerInputNameState,
|
||||
viewPickerIsPersistingState,
|
||||
viewPickerKanbanFieldMetadataIdState,
|
||||
viewPickerSelectedIconState,
|
||||
viewPickerTypeState,
|
||||
],
|
||||
);
|
||||
|
||||
const handleDelete = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async () => {
|
||||
set(viewPickerIsPersistingState, true);
|
||||
const viewPickerReferenceViewId = getSnapshotValue(
|
||||
snapshot,
|
||||
viewPickerReferenceViewIdState,
|
||||
);
|
||||
|
||||
selectView(
|
||||
viewsOnCurrentObject.filter(
|
||||
(view) => view.id !== viewPickerReferenceViewId,
|
||||
)[0].id,
|
||||
);
|
||||
await removeView(viewPickerReferenceViewId);
|
||||
closeAndResetViewPicker();
|
||||
},
|
||||
[
|
||||
closeAndResetViewPicker,
|
||||
removeView,
|
||||
selectView,
|
||||
viewPickerIsPersistingState,
|
||||
viewPickerReferenceViewIdState,
|
||||
viewsOnCurrentObject,
|
||||
],
|
||||
);
|
||||
|
||||
const handleUpdate = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async () => {
|
||||
set(viewPickerIsPersistingState, true);
|
||||
|
||||
const viewPickerReferenceViewId = getSnapshotValue(
|
||||
snapshot,
|
||||
viewPickerReferenceViewIdState,
|
||||
);
|
||||
const viewPickerInputName = getSnapshotValue(
|
||||
snapshot,
|
||||
viewPickerInputNameState,
|
||||
);
|
||||
const viewPickerSelectedIcon = getSnapshotValue(
|
||||
snapshot,
|
||||
viewPickerSelectedIconState,
|
||||
);
|
||||
|
||||
await updateView({
|
||||
id: viewPickerReferenceViewId,
|
||||
name: viewPickerInputName,
|
||||
icon: viewPickerSelectedIcon,
|
||||
});
|
||||
selectView(viewPickerReferenceViewId);
|
||||
closeAndResetViewPicker();
|
||||
},
|
||||
[
|
||||
viewPickerIsPersistingState,
|
||||
viewPickerReferenceViewIdState,
|
||||
viewPickerInputNameState,
|
||||
viewPickerSelectedIconState,
|
||||
updateView,
|
||||
selectView,
|
||||
closeAndResetViewPicker,
|
||||
],
|
||||
);
|
||||
|
||||
return { handleCreate, handleDelete, handleUpdate };
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { viewPickerInputNameComponentState } from '@/views/view-picker/states/viewPickerInputNameComponentState';
|
||||
import { viewPickerIsPersistingComponentState } from '@/views/view-picker/states/viewPickerIsPersistingComponentState';
|
||||
import { viewPickerKanbanFieldMetadataIdComponentState } from '@/views/view-picker/states/viewPickerKanbanFieldMetadataIdComponentState';
|
||||
import { viewPickerModeComponentState } from '@/views/view-picker/states/viewPickerModeComponentState';
|
||||
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 { ViewScopeInternalContext } from '../../scopes/scope-internal-context/ViewScopeInternalContext';
|
||||
|
||||
export const useViewPickerStates = (viewComponentId?: string) => {
|
||||
const componentId = useAvailableScopeIdOrThrow(
|
||||
ViewScopeInternalContext,
|
||||
viewComponentId,
|
||||
);
|
||||
|
||||
return {
|
||||
componentId,
|
||||
viewPickerModeState: extractComponentState(
|
||||
viewPickerModeComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewPickerInputNameState: extractComponentState(
|
||||
viewPickerInputNameComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewPickerSelectedIconState: extractComponentState(
|
||||
viewPickerSelectedIconComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewPickerKanbanFieldMetadataIdState: extractComponentState(
|
||||
viewPickerKanbanFieldMetadataIdComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewPickerReferenceViewIdState: extractComponentState(
|
||||
viewPickerReferenceViewIdComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewPickerIsPersistingState: extractComponentState(
|
||||
viewPickerIsPersistingComponentState,
|
||||
componentId,
|
||||
),
|
||||
viewPickerTypeState: extractComponentState(
|
||||
viewPickerTypeComponentState,
|
||||
componentId,
|
||||
),
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const viewPickerInputNameComponentState = createComponentState<string>({
|
||||
key: 'viewPickerInputNameComponentState',
|
||||
defaultValue: '',
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const viewPickerIsPersistingComponentState =
|
||||
createComponentState<boolean>({
|
||||
key: 'viewPickerIsPersistingComponentState',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const viewPickerKanbanFieldMetadataIdComponentState =
|
||||
createComponentState<string>({
|
||||
key: 'viewPickerKanbanFieldMetadataIdComponentState',
|
||||
defaultValue: '',
|
||||
});
|
||||
@ -1,8 +1,8 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const viewEditModeComponentState = createComponentState<
|
||||
'none' | 'edit' | 'create'
|
||||
export const viewPickerModeComponentState = createComponentState<
|
||||
'list' | 'edit' | 'create'
|
||||
>({
|
||||
key: 'viewEditModeComponentState',
|
||||
defaultValue: 'none',
|
||||
defaultValue: 'list',
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const viewPickerReferenceViewIdComponentState =
|
||||
createComponentState<string>({
|
||||
key: 'viewPickerReferenceViewIdComponentState',
|
||||
defaultValue: '',
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const viewPickerSelectedIconComponentState =
|
||||
createComponentState<string>({
|
||||
key: 'viewPickerSelectedIconComponentState',
|
||||
defaultValue: '',
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
export const viewPickerTypeComponentState = createComponentState<ViewType>({
|
||||
key: 'viewPickerTypeComponentState',
|
||||
defaultValue: ViewType.Table,
|
||||
});
|
||||
Reference in New Issue
Block a user