Complete Fix view work (#2272)

* Fix views

* Make view sorts and view filters functional

* Complete Company table view fix

* Fix model creation

* Start fixing board

* Complete work
This commit is contained in:
Charles Bochet
2023-10-29 16:29:00 +01:00
committed by GitHub
parent 685d342170
commit 9bab28912d
118 changed files with 1806 additions and 1413 deletions

View File

@ -10,7 +10,7 @@ import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '../hooks/useViewInternalStates';
import { useViewGetStates } from '../hooks/useViewGetStates';
const StyledContainer = styled.div`
display: inline-flex;
@ -28,7 +28,7 @@ export const UpdateViewButtonGroup = ({
}: UpdateViewButtonGroupProps) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const { updateCurrentView, setViewEditMode } = useView();
const { canPersistFilters, canPersistSorts } = useViewInternalStates();
const { canPersistFilters, canPersistSorts } = useViewGetStates();
const canPersistView = canPersistFilters || canPersistSorts;

View File

@ -9,7 +9,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { TopBar } from '@/ui/layout/top-bar/TopBar';
import { useView } from '../hooks/useView';
import { useViewInternalStates } from '../hooks/useViewInternalStates';
import { useViewGetStates } from '../hooks/useViewGetStates';
import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
@ -31,18 +31,20 @@ export const ViewBar = ({
const { openDropdown: openOptionsDropdownButton } = useDropdown({
dropdownScopeId: optionsDropdownScopeId,
});
const { upsertViewSort } = useView();
const { availableFilters, availableSorts } = useViewInternalStates();
const { upsertViewSort, upsertViewFilter } = useView();
const { availableFilterDefinitions, availableSortDefinitions } =
useViewGetStates();
return (
<FilterScope
filterScopeId="view-filter"
availableFilters={availableFilters}
availableFilterDefinitions={availableFilterDefinitions}
onFilterSelect={upsertViewFilter}
>
<SortScope
sortScopeId="view-sort"
availableSorts={availableSorts}
onSortAdd={upsertViewSort}
availableSortDefinitions={availableSortDefinitions}
onSortSelect={upsertViewSort}
>
<ViewBarEffect />
<TopBar
@ -51,6 +53,7 @@ export const ViewBar = ({
<ViewsDropdownButton
onViewEditModeChange={openOptionsDropdownButton}
hotkeyScope={{ scope: ViewsHotkeyScope.ListDropdown }}
optionsDropdownScopeId={optionsDropdownScopeId}
/>
}
displayBottomBorder={false}

View File

@ -5,9 +5,8 @@ import { AddFilterFromDropdownButton } from '@/ui/data/filter/components/AddFilt
import { getOperandLabelShort } from '@/ui/data/filter/utils/getOperandLabel';
import { IconArrowDown, IconArrowUp } from '@/ui/display/icon/index';
import { useRemoveFilter } from '../hooks/useRemoveFilter';
import { useView } from '../hooks/useView';
import { useViewInternalStates } from '../hooks/useViewInternalStates';
import { useViewGetStates } from '../hooks/useViewGetStates';
import SortOrFilterChip from './SortOrFilterChip';
@ -90,43 +89,23 @@ export const ViewBarDetails = ({
}: ViewBarDetailsProps) => {
const {
currentViewSorts,
setCurrentViewSorts,
availableFilters,
currentViewFilters,
canPersistFilters,
canPersistSorts,
isViewBarExpanded,
} = useViewInternalStates();
} = useViewGetStates();
const { resetViewBar } = useView();
const { resetViewBar, removeViewSort, removeViewFilter } = useView();
const canPersistView = canPersistFilters || canPersistSorts;
const filtersWithDefinition = currentViewFilters?.map((filter) => {
const filterDefinition = availableFilters.find((availableFilter) => {
return availableFilter.key === filter.key;
});
return {
...filter,
...filterDefinition,
};
});
const removeFilter = useRemoveFilter();
const handleCancelClick = () => {
resetViewBar();
};
const handleSortRemove = (sortKey: string) =>
setCurrentViewSorts?.((previousSorts) =>
previousSorts.filter((sort) => sort.key !== sortKey),
);
const shouldExpandViewBar =
canPersistView ||
((filtersWithDefinition?.length || currentViewSorts?.length) &&
((currentViewSorts?.length || currentViewFilters?.length) &&
isViewBarExpanded);
if (!shouldExpandViewBar) {
@ -140,32 +119,32 @@ export const ViewBarDetails = ({
{currentViewSorts?.map((sort) => {
return (
<SortOrFilterChip
key={sort.key}
testId={sort.key}
key={sort.fieldId}
testId={sort.fieldId}
labelValue={sort.definition.label}
Icon={sort.direction === 'desc' ? IconArrowDown : IconArrowUp}
isSort
onRemove={() => handleSortRemove(sort.key)}
onRemove={() => removeViewSort(sort.fieldId)}
/>
);
})}
{!!currentViewSorts?.length && !!filtersWithDefinition?.length && (
{!!currentViewSorts?.length && !!currentViewFilters?.length && (
<StyledSeperatorContainer>
<StyledSeperator />
</StyledSeperatorContainer>
)}
{filtersWithDefinition?.map((filter) => {
{currentViewFilters?.map((filter) => {
return (
<SortOrFilterChip
key={filter.key}
testId={filter.key}
labelKey={filter.label}
key={filter.fieldId}
testId={filter.fieldId}
labelKey={filter.definition.label}
labelValue={`${getOperandLabelShort(filter.operand)} ${
filter.displayValue
}`}
Icon={filter.Icon}
Icon={filter.definition.Icon}
onRemove={() => {
removeFilter(filter.key);
removeViewFilter(filter.fieldId);
}}
/>
);

View File

@ -8,28 +8,65 @@ import { assertNotNull } from '~/utils/assert';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { useView } from '../hooks/useView';
import { useViewInternalStates } from '../hooks/useViewInternalStates';
import { availableFieldsScopedState } from '../states/availableFieldsScopedState';
import { useViewGetStates } from '../hooks/useViewGetStates';
import { availableFieldDefinitionsScopedState } from '../states/availableFieldDefinitionsScopedState';
import { availableFilterDefinitionsScopedState } from '../states/availableFilterDefinitionsScopedState';
import { availableSortDefinitionsScopedState } from '../states/availableSortDefinitionsScopedState';
import { onViewFieldsChangeScopedState } from '../states/onViewFieldsChangeScopedState';
import { onViewFiltersChangeScopedState } from '../states/onViewFiltersChangeScopedState';
import { onViewSortsChangeScopedState } from '../states/onViewSortsChangeScopedState';
import { savedViewFieldsScopedFamilyState } from '../states/savedViewFieldsScopedFamilyState';
import { savedViewFiltersScopedFamilyState } from '../states/savedViewFiltersScopedFamilyState';
import { savedViewSortsScopedFamilyState } from '../states/savedViewSortsScopedFamilyState';
import { viewsScopedState } from '../states/viewsScopedState';
import { View } from '../types/View';
import { ViewField } from '../types/ViewField';
import { ViewFilter } from '../types/ViewFilter';
import { ViewSort } from '../types/ViewSort';
export const ViewBarEffect = () => {
const {
scopeId: viewScopeId,
setCurrentViewFields,
setSavedViewFields,
setCurrentViewFilters,
setSavedViewFilters,
setCurrentViewSorts,
setSavedViewSorts,
currentViewId,
setViews,
changeView,
loadView,
changeViewInUrl,
setCurrentViewId,
} = useView();
const [searchParams] = useSearchParams();
const { viewType, viewObjectId } = useViewInternalStates(viewScopeId);
const { viewType, viewObjectId } = useViewGetStates(viewScopeId);
useFindManyObjects({
objectNamePlural: 'viewsV2',
filter: { type: { eq: viewType }, objectId: { eq: viewObjectId } },
onCompleted: useRecoilCallback(
({ snapshot }) =>
async (data: PaginatedObjectTypeResults<View>) => {
const nextViews = data.edges.map((view) => ({
id: view.node.id,
name: view.node.name,
objectId: view.node.objectId,
}));
const views = snapshot
.getLoadable(viewsScopedState({ scopeId: viewScopeId }))
.getValue();
if (!isDeeplyEqual(views, nextViews)) setViews(nextViews);
if (!nextViews.length) return;
if (!currentViewId) return changeViewInUrl(nextViews[0].id);
},
),
});
useFindManyObjects({
objectNamePlural: 'viewFieldsV2',
@ -38,7 +75,9 @@ export const ViewBarEffect = () => {
({ snapshot }) =>
async (data: PaginatedObjectTypeResults<ViewField>) => {
const availableFields = snapshot
.getLoadable(availableFieldsScopedState({ scopeId: viewScopeId }))
.getLoadable(
availableFieldDefinitionsScopedState({ scopeId: viewScopeId }),
)
.getValue();
const onViewFieldsChange = snapshot
@ -74,133 +113,122 @@ export const ViewBarEffect = () => {
});
useFindManyObjects({
objectNamePlural: 'viewsV2',
filter: { type: { eq: viewType }, objectId: { eq: viewObjectId } },
objectNamePlural: 'viewFiltersV2',
filter: { viewId: { eq: currentViewId } },
onCompleted: useRecoilCallback(
({ snapshot }) =>
async (data: PaginatedObjectTypeResults<View>) => {
const nextViews = data.edges.map((view) => ({
id: view.node.id,
name: view.node.name,
objectId: view.node.objectId,
}));
const views = snapshot
.getLoadable(viewsScopedState({ scopeId: viewScopeId }))
async (data: PaginatedObjectTypeResults<Required<ViewFilter>>) => {
const availableFilterDefinitions = snapshot
.getLoadable(
availableFilterDefinitionsScopedState({ scopeId: viewScopeId }),
)
.getValue();
if (!isDeeplyEqual(views, nextViews)) setViews(nextViews);
if (!availableFilterDefinitions || !currentViewId) {
return;
}
if (!nextViews.length) return;
const savedViewFilters = snapshot
.getLoadable(
savedViewFiltersScopedFamilyState({
scopeId: viewScopeId,
familyKey: currentViewId,
}),
)
.getValue();
if (!currentViewId) return changeView(nextViews[0].id);
const onViewFiltersChange = snapshot
.getLoadable(
onViewFiltersChangeScopedState({ scopeId: viewScopeId }),
)
.getValue();
const queriedViewFilters = data.edges
.map(({ node }) => {
const availableFilterDefinition = availableFilterDefinitions.find(
(filterDefinition) => filterDefinition.fieldId === node.fieldId,
);
if (!availableFilterDefinition) return null;
return {
...node,
displayValue: node.displayValue ?? node.value,
definition: availableFilterDefinition,
};
})
.filter(assertNotNull);
if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) {
setSavedViewFilters?.(queriedViewFilters);
setCurrentViewFilters?.(queriedViewFilters);
onViewFiltersChange?.(queriedViewFilters);
}
},
),
});
// useGetViewSortsQuery({
// skip: !currentViewId,
// variables: {
// where: {
// viewId: { equals: currentViewId },
// },
// },
// onCompleted: useRecoilCallback(({ snapshot }) => async (data) => {
// const availableSorts = snapshot
// .getLoadable(availableSortsScopedState({ scopeId: viewScopeId }))
// .getValue();
useFindManyObjects({
objectNamePlural: 'viewSortsV2',
filter: { viewId: { eq: currentViewId } },
onCompleted: useRecoilCallback(
({ snapshot }) =>
async (data: PaginatedObjectTypeResults<Required<ViewSort>>) => {
const availableSortDefinitions = snapshot
.getLoadable(
availableSortDefinitionsScopedState({ scopeId: viewScopeId }),
)
.getValue();
// if (!availableSorts || !currentViewId) {
// return;
// }
if (!availableSortDefinitions || !currentViewId) {
return;
}
// const savedViewSorts = snapshot
// .getLoadable(
// savedViewSortsScopedFamilyState({
// scopeId: viewScopeId,
// familyKey: currentViewId,
// }),
// )
// .getValue();
const savedViewSorts = snapshot
.getLoadable(
savedViewSortsScopedFamilyState({
scopeId: viewScopeId,
familyKey: currentViewId,
}),
)
.getValue();
// const queriedViewSorts = data.viewSorts
// .map((viewSort) => {
// const foundCorrespondingSortDefinition = availableSorts.find(
// (sort) => sort.key === viewSort.key,
// );
const onViewSortsChange = snapshot
.getLoadable(onViewSortsChangeScopedState({ scopeId: viewScopeId }))
.getValue();
// if (foundCorrespondingSortDefinition) {
// return {
// key: viewSort.key,
// definition: foundCorrespondingSortDefinition,
// direction: viewSort.direction.toLowerCase(),
// } as Sort;
// } else {
// return undefined;
// }
// })
// .filter((sort): sort is Sort => !!sort);
const queriedViewSorts = data.edges
.map(({ node }) => {
const availableSortDefinition = availableSortDefinitions.find(
(sort) => sort.fieldId === node.fieldId,
);
// if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) {
// setSavedViewSorts?.(queriedViewSorts);
// setCurrentViewSorts?.(queriedViewSorts);
// }
// }),
// });
if (!availableSortDefinition) return null;
// useGetViewFiltersQuery({
// skip: !currentViewId,
// variables: {
// where: {
// viewId: { equals: currentViewId },
// },
// },
// onCompleted: useRecoilCallback(({ snapshot }) => (data) => {
// const availableFilters = snapshot
// .getLoadable(availableFiltersScopedState({ scopeId: viewScopeId }))
// .getValue();
return {
id: node.id,
fieldId: node.fieldId,
direction: node.direction,
definition: availableSortDefinition,
};
})
.filter(assertNotNull);
// if (!availableFilters || !currentViewId) {
// return;
// }
// const savedViewFilters = snapshot
// .getLoadable(
// savedViewFiltersScopedFamilyState({
// scopeId: viewScopeId,
// familyKey: currentViewId,
// }),
// )
// .getValue();
// const queriedViewFilters = data.viewFilters
// .map(({ __typename, name: _name, ...viewFilter }) => {
// const availableFilter = availableFilters.find(
// (filter) => filter.key === viewFilter.key,
// );
// return availableFilter
// ? {
// ...viewFilter,
// displayValue: viewFilter.displayValue ?? viewFilter.value,
// type: availableFilter.type,
// }
// : undefined;
// })
// .filter((filter): filter is Filter => !!filter);
// if (!isDeeplyEqual(savedViewFilters, queriedViewFilters)) {
// setSavedViewFilters?.(queriedViewFilters);
// setCurrentViewFilters?.(queriedViewFilters);
// }
// }),
// });
if (!isDeeplyEqual(savedViewSorts, queriedViewSorts)) {
setSavedViewSorts?.(queriedViewSorts);
setCurrentViewSorts?.(queriedViewSorts);
onViewSortsChange?.(queriedViewSorts);
}
},
),
});
const currentViewIdFromUrl = searchParams.get('view');
useEffect(() => {
if (!currentViewIdFromUrl) return;
setCurrentViewId(currentViewIdFromUrl);
}, [currentViewIdFromUrl, setCurrentViewId]);
loadView(currentViewIdFromUrl);
}, [currentViewIdFromUrl, loadView, setCurrentViewId]);
return <></>;
};

View File

@ -3,7 +3,6 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilCallback } from 'recoil';
import { TableOptionsDropdownId } from '@/ui/data/data-table/constants/TableOptionsDropdownId';
import {
IconChevronDown,
IconList,
@ -24,7 +23,7 @@ import { assertNotNull } from '~/utils/assert';
import { ViewsDropdownId } from '../constants/ViewsDropdownId';
import { useView } from '../hooks/useView';
import { useViewInternalStates } from '../hooks/useViewInternalStates';
import { useViewGetStates } from '../hooks/useViewGetStates';
const StyledBoldDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
font-weight: ${({ theme }) => theme.font.weight.regular};
@ -60,17 +59,22 @@ const StyledViewName = styled.span`
export type ViewsDropdownButtonProps = {
hotkeyScope: HotkeyScope;
onViewEditModeChange?: () => void;
optionsDropdownScopeId: string;
};
export const ViewsDropdownButton = ({
hotkeyScope,
onViewEditModeChange,
optionsDropdownScopeId,
}: ViewsDropdownButtonProps) => {
const theme = useTheme();
const { scopeId, removeView, currentViewId, changeView } = useView();
const { scopeId, removeView, currentViewId, changeViewInUrl } = useView();
const { views, currentView, setViewEditMode, entityCountInCurrentView } =
useViewInternalStates(scopeId, currentViewId);
const { views, currentView, entityCountInCurrentView } = useViewGetStates(
scopeId,
currentViewId,
);
const { setViewEditMode } = useView();
const {
isDropdownOpen: isViewsDropdownOpen,
@ -80,16 +84,16 @@ export const ViewsDropdownButton = ({
});
const { openDropdown: openOptionsDropdown } = useDropdown({
dropdownScopeId: TableOptionsDropdownId,
dropdownScopeId: optionsDropdownScopeId,
});
const handleViewSelect = useRecoilCallback(
() => async (viewId: string) => {
changeView(viewId);
changeViewInUrl(viewId);
closeViewsDropdown();
},
[changeView, closeViewsDropdown],
[changeViewInUrl, closeViewsDropdown],
);
const handleAddViewButtonClick = () => {
@ -104,7 +108,7 @@ export const ViewsDropdownButton = ({
viewId: string,
) => {
event.stopPropagation();
changeView(viewId);
changeViewInUrl(viewId);
setViewEditMode('edit');
onViewEditModeChange?.();
closeViewsDropdown();