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

@ -4,17 +4,13 @@ import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { ActivityType } from '~/generated/graphql';
export const PageAddTaskButton = () => {
const { selectedFilters } = useFilter();
const { selectedFilter } = useFilter();
const openCreateActivity = useOpenCreateActivityDrawer();
const assigneeIdFilter = selectedFilters.find(
(filter) => filter.key === 'assigneeId',
);
const handleClick = () => {
openCreateActivity({
type: ActivityType.Task,
assigneeId: assigneeIdFilter?.value,
assigneeId: selectedFilter?.value,
});
};

View File

@ -17,12 +17,14 @@ export const useCurrentUserTaskCount = () => {
completedAt: { equals: null },
...(currentUser
? turnFilterIntoWhereClause({
key: 'assigneeId',
type: 'entity',
fieldId: 'assigneeId',
value: currentUser.id,
operand: ViewFilterOperand.Is,
displayValue: currentUser.displayName,
displayAvatarUrl: currentUser.avatarUrl ?? undefined,
definition: {
type: 'entity',
},
})
: {}),
},

View File

@ -7,7 +7,7 @@ import { ActivityType, useGetActivitiesQuery } from '~/generated/graphql';
import { parseDate } from '~/utils/date-utils';
export const useTasks = (entity?: ActivityTargetableEntity) => {
const { selectedFilters } = useFilter();
const { selectedFilter } = useFilter();
const whereFilters = entity
? {
@ -20,12 +20,7 @@ export const useTasks = (entity?: ActivityTargetableEntity) => {
},
},
}
: Object.assign(
{},
...selectedFilters.map((filter) => {
return turnFilterIntoWhereClause(filter);
}),
);
: Object.assign({}, turnFilterIntoWhereClause(selectedFilter));
const { data: completeTasksData } = useGetActivitiesQuery({
variables: {
@ -35,7 +30,7 @@ export const useTasks = (entity?: ActivityTargetableEntity) => {
...whereFilters,
},
},
skip: !entity && selectedFilters.length === 0,
skip: !entity && !selectedFilter,
});
const { data: incompleteTaskData } = useGetActivitiesQuery({
@ -46,7 +41,7 @@ export const useTasks = (entity?: ActivityTargetableEntity) => {
...whereFilters,
},
},
skip: !entity && selectedFilters.length === 0,
skip: !entity && !selectedFilter,
});
const todayOrPreviousTasks = incompleteTaskData?.findManyActivities.filter(

View File

@ -1,15 +1,29 @@
import styled from '@emotion/styled';
import { BoardContext } from '@/companies/states/contexts/BoardContext';
import { BoardOptionsDropdown } from '@/ui/layout/board/components/BoardOptionsDropdown';
import { BoardOptionsDropdownId } from '@/ui/layout/board/components/constants/BoardOptionsDropdownId';
import {
EntityBoard,
EntityBoardProps,
} from '@/ui/layout/board/components/EntityBoard';
import { EntityBoardActionBar } from '@/ui/layout/board/components/EntityBoardActionBar';
import { EntityBoardContextMenu } from '@/ui/layout/board/components/EntityBoardContextMenu';
import { ViewBar } from '@/views/components/ViewBar';
import { ViewScope } from '@/views/scopes/ViewScope';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { HooksCompanyBoardEffect } from '../../components/HooksCompanyBoardEffect';
import { CompanyBoardRecoilScopeContext } from '../../states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
width: 100%;
`;
type CompanyBoardProps = Pick<
EntityBoardProps,
'onColumnAdd' | 'onColumnDelete' | 'onEditColumnTitle'
@ -20,24 +34,30 @@ export const CompanyBoard = ({
onColumnDelete,
onEditColumnTitle,
}: CompanyBoardProps) => {
const viewScopeId = 'company-board-view';
return (
<>
<BoardContext.Provider
value={{
BoardRecoilScopeContext: CompanyBoardRecoilScopeContext,
}}
>
<HooksCompanyBoardEffect />
<EntityBoard
boardOptions={opportunitiesBoardOptions}
onColumnAdd={onColumnAdd}
onColumnDelete={onColumnDelete}
onEditColumnTitle={onEditColumnTitle}
/>
<EntityBoardActionBar />
<EntityBoardContextMenu />
</BoardContext.Provider>
</>
<ViewScope viewScopeId={viewScopeId}>
<StyledContainer>
<BoardContext.Provider
value={{
BoardRecoilScopeContext: CompanyBoardRecoilScopeContext,
}}
>
<ViewBar
optionsDropdownButton={<BoardOptionsDropdown />}
optionsDropdownScopeId={BoardOptionsDropdownId}
/>
<HooksCompanyBoardEffect />
<EntityBoard
boardOptions={opportunitiesBoardOptions}
onColumnAdd={onColumnAdd}
onColumnDelete={onColumnDelete}
onEditColumnTitle={onEditColumnTitle}
/>
<EntityBoardActionBar />
<EntityBoardContextMenu />
</BoardContext.Provider>
</StyledContainer>
</ViewScope>
);
};

View File

@ -2,12 +2,19 @@ import { useEffect, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { pipelineAvailableFieldDefinitions } from '@/pipeline/constants/pipelineAvailableFieldDefinitions';
import { turnFilterIntoWhereClause } from '@/ui/data/filter/utils/turnFilterIntoWhereClause';
import { useBoardActionBarEntries } from '@/ui/layout/board/hooks/useBoardActionBarEntries';
import { useBoardContext } from '@/ui/layout/board/hooks/useBoardContext';
import { useBoardContextMenuEntries } from '@/ui/layout/board/hooks/useBoardContextMenuEntries';
import { availableBoardCardFieldsScopedState } from '@/ui/layout/board/states/availableBoardCardFieldsScopedState';
import { boardCardFieldsScopedState } from '@/ui/layout/board/states/boardCardFieldsScopedState';
import { isBoardLoadedState } from '@/ui/layout/board/states/isBoardLoadedState';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { useViewGetStates } from '@/views/hooks/useViewGetStates';
import { ViewType } from '@/views/types/ViewType';
import { viewFieldsToBoardFieldDefinitions } from '@/views/utils/viewFieldsToBoardFieldDefinitions';
import {
Pipeline,
PipelineProgressableType,
@ -22,20 +29,30 @@ import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
export const HooksCompanyBoardEffect = () => {
const {
setAvailableFilters,
setAvailableSorts,
setAvailableFilterDefinitions,
setAvailableSortDefinitions,
setAvailableFieldDefinitions,
setEntityCountInCurrentView,
setViewObjectId,
setViewType,
} = useView();
const { currentViewFilters } = useViewInternalStates();
useEffect(() => {
setAvailableFilters(opportunitiesBoardOptions.filters);
setAvailableSorts?.(opportunitiesBoardOptions.sorts);
}, [setAvailableFilters, setAvailableSorts]);
const { currentViewFilters, currentViewFields } = useViewGetStates();
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
const { BoardRecoilScopeContext } = useBoardContext();
const [, setBoardCardFields] = useRecoilScopedState(
boardCardFieldsScopedState,
BoardRecoilScopeContext,
);
const [, setAvailableBoardCardFields] = useRecoilScopedState(
availableBoardCardFieldsScopedState,
BoardRecoilScopeContext,
);
const updateCompanyBoard = useUpdateCompanyBoard();
const { data: pipelineData, loading: loadingGetPipelines } =
@ -51,6 +68,21 @@ export const HooksCompanyBoardEffect = () => {
const pipeline = pipelineData?.findManyPipeline[0] as Pipeline | undefined;
useEffect(() => {
setAvailableFilterDefinitions(opportunitiesBoardOptions.filterDefinitions);
setAvailableSortDefinitions?.(opportunitiesBoardOptions.sortDefinitions);
setAvailableFieldDefinitions?.(pipelineAvailableFieldDefinitions);
}, [
setAvailableFieldDefinitions,
setAvailableFilterDefinitions,
setAvailableSortDefinitions,
]);
useEffect(() => {
setViewObjectId?.('company');
setViewType?.(ViewType.Kanban);
}, [setViewObjectId, setViewType]);
const pipelineStageIds = pipeline?.pipelineStages
?.map((pipelineStage) => pipelineStage.id)
.flat();
@ -107,6 +139,7 @@ export const HooksCompanyBoardEffect = () => {
if (!loading && pipeline && pipelineProgresses && companiesData) {
setActionBarEntries();
setContextMenuEntries();
setAvailableBoardCardFields(pipelineAvailableFieldDefinitions);
updateCompanyBoard(pipeline, pipelineProgresses, companiesData.companies);
setEntityCountInCurrentView(companiesData.companies.length);
}
@ -120,7 +153,19 @@ export const HooksCompanyBoardEffect = () => {
setContextMenuEntries,
searchParams,
setEntityCountInCurrentView,
setAvailableBoardCardFields,
]);
useEffect(() => {
if (currentViewFields) {
setBoardCardFields(
viewFieldsToBoardFieldDefinitions(
currentViewFields,
pipelineAvailableFieldDefinitions,
),
);
}
}, [currentViewFields, setBoardCardFields]);
return <></>;
};

View File

@ -8,40 +8,59 @@ import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyT
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
import { DataTable } from '@/ui/data/data-table/components/DataTable';
import { DataTableEffect } from '@/ui/data/data-table/components/DataTableEffect';
import { TableOptionsDropdownId } from '@/ui/data/data-table/constants/TableOptionsDropdownId';
import { TableContext } from '@/ui/data/data-table/contexts/TableContext';
import { useUpsertDataTableItem } from '@/ui/data/data-table/hooks/useUpsertDataTableItem';
import { TableOptionsDropdown } from '@/ui/data/data-table/options/components/TableOptionsDropdown';
import { tableColumnsScopedState } from '@/ui/data/data-table/states/tableColumnsScopedState';
import { tableFiltersScopedState } from '@/ui/data/data-table/states/tableFiltersScopedState';
import { tableSortsScopedState } from '@/ui/data/data-table/states/tableSortsScopedState';
import { ViewBar } from '@/views/components/ViewBar';
import { useViewFields } from '@/views/hooks/internal/useViewFields';
import { useView } from '@/views/hooks/useView';
import { ViewScope } from '@/views/scopes/ViewScope';
import { columnDefinitionsToViewFields } from '@/views/utils/columnDefinitionToViewField';
import { viewFieldsToColumnDefinitions } from '@/views/utils/viewFieldsToColumnDefinitions';
import { viewFiltersToFilters } from '@/views/utils/viewFiltersToFilters';
import { viewSortsToSorts } from '@/views/utils/viewSortsToSorts';
import {
UpdateOneCompanyMutationVariables,
useGetCompaniesQuery,
useGetWorkspaceMembersLazyQuery,
useUpdateOneCompanyMutation,
} from '~/generated/graphql';
import { companyAvailableFilters } from '~/pages/companies/companies-filters';
import { companyAvailableSorts } from '~/pages/companies/companies-sorts';
import { companyTableFilterDefinitions } from '~/pages/companies/constants/companyTableFilterDefinitions';
import { companyTableSortDefinitions } from '~/pages/companies/constants/companyTableSortDefinitions';
import CompanyTableEffect from './CompanyTableEffect';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
export const CompanyTable = () => {
const tableViewScopeId = 'company-table';
const viewScopeId = 'company-table-view';
const tableScopeId = 'companies';
const setTableColumns = useSetRecoilState(
tableColumnsScopedState('companies'),
tableColumnsScopedState(tableScopeId),
);
const setTableFilters = useSetRecoilState(
tableFiltersScopedState(tableScopeId),
);
const setTableSorts = useSetRecoilState(tableSortsScopedState(tableScopeId));
const [updateEntityMutation] = useUpdateOneCompanyMutation();
const upsertDataTableItem = useUpsertDataTableItem();
const [getWorkspaceMember] = useGetWorkspaceMembersLazyQuery();
const { persistViewFields } = useViewFields(tableViewScopeId);
const { setCurrentViewFields, currentViewId } = useView({
viewScopeId: tableViewScopeId,
const { persistViewFields } = useViewFields(viewScopeId);
const { setCurrentViewFields } = useView({
viewScopeId,
});
const { setContextMenuEntries } = useCompanyTableContextMenuEntries();
@ -79,16 +98,9 @@ export const CompanyTable = () => {
const { openCompanySpreadsheetImport: onImport } =
useSpreadsheetCompanyImport();
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
return (
<ViewScope
viewScopeId={tableViewScopeId}
viewScopeId={viewScopeId}
onViewFieldsChange={(viewFields) => {
setTableColumns(
viewFieldsToColumnDefinitions(
@ -97,7 +109,12 @@ export const CompanyTable = () => {
),
);
}}
onViewFiltersChange={() => {}}
onViewFiltersChange={(viewFilters) => {
setTableFilters(viewFiltersToFilters(viewFilters));
}}
onViewSortsChange={(viewSorts) => {
setTableSorts(viewSortsToSorts(viewSorts));
}}
>
<StyledContainer>
<TableContext.Provider
@ -110,7 +127,7 @@ export const CompanyTable = () => {
>
<ViewBar
optionsDropdownButton={<TableOptionsDropdown onImport={onImport} />}
optionsDropdownScopeId="table-dropdown-option"
optionsDropdownScopeId={TableOptionsDropdownId}
/>
<CompanyTableEffect />
<DataTableEffect
@ -119,8 +136,8 @@ export const CompanyTable = () => {
getRequestOptimisticEffectDefinition={
getCompaniesOptimisticEffectDefinition
}
filterDefinitionArray={companyAvailableFilters}
sortDefinitionArray={companyAvailableSorts}
filterDefinitionArray={companyTableFilterDefinitions}
sortDefinitionArray={companyTableSortDefinitions}
setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries}
/>

View File

@ -6,14 +6,14 @@ import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scop
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useView } from '@/views/hooks/useView';
import { ViewType } from '@/views/types/ViewType';
import { companyAvailableFilters } from '~/pages/companies/companies-filters';
import { companyAvailableSorts } from '~/pages/companies/companies-sorts';
import { companyTableFilterDefinitions } from '~/pages/companies/constants/companyTableFilterDefinitions';
import { companyTableSortDefinitions } from '~/pages/companies/constants/companyTableSortDefinitions';
const CompanyTableEffect = () => {
const {
setAvailableSorts,
setAvailableFilters,
setAvailableFields,
setAvailableSortDefinitions,
setAvailableFilterDefinitions,
setAvailableFieldDefinitions,
setViewType,
setViewObjectId,
} = useView();
@ -24,26 +24,21 @@ const CompanyTableEffect = () => {
);
useEffect(() => {
setAvailableSorts?.(companyAvailableSorts);
setAvailableFilters?.(companyAvailableFilters);
setAvailableFields?.(companiesAvailableFieldDefinitions);
setAvailableSortDefinitions?.(companyTableSortDefinitions);
setAvailableFilterDefinitions?.(companyTableFilterDefinitions);
setAvailableFieldDefinitions?.(companiesAvailableFieldDefinitions);
setViewObjectId?.('company');
setViewType?.(ViewType.Table);
setAvailableTableColumns(companiesAvailableFieldDefinitions);
}, [
setAvailableFields,
setAvailableFilters,
setAvailableSorts,
setAvailableFieldDefinitions,
setAvailableFilterDefinitions,
setAvailableSortDefinitions,
setAvailableTableColumns,
setViewObjectId,
setViewType,
]);
// useEffect(() => {
// if (currentViewFields) {
// setTableColumns([...currentViewFields].sort((a, b) => a.index - b.index));
// }
// }, [currentViewFields, setTableColumns]);
// useEffect(() => {
// if (currentViewSorts) {

View File

@ -17,7 +17,6 @@ export const CompanyTableMockDataEffect = () => {
useEffect(() => {
setDataTableData(mockedCompaniesData, [], []);
setTableColumns(companiesAvailableFieldDefinitions);
}, [setDataTableData, setTableColumns]);

View File

@ -1,6 +1,7 @@
import { useNavigate } from 'react-router-dom';
import { IconBuildingSkyscraper } from '@/ui/display/icon';
import { Icon123 } from '@/ui/input/constants/icons';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import NavItem from '@/ui/navigation/navbar/components/NavItem';
import { capitalize } from '~/utils/string/capitalize';
@ -10,22 +11,26 @@ export const MetadataObjectNavItems = () => {
const { metadataObjects } = useFindManyMetadataObjects();
const navigate = useNavigate();
const { icons } = useLazyLoadIcons();
return (
<>
{metadataObjects
.filter((metadataObject) => !!metadataObject.isActive)
.map((metadataObject) => (
<NavItem
key={metadataObject.id}
label={capitalize(metadataObject.namePlural)}
to={`/objects/${metadataObject.namePlural}`}
Icon={IconBuildingSkyscraper}
onClick={() => {
navigate(`/objects/${metadataObject.namePlural}`);
}}
/>
))}
.filter((metadataObjects) => !metadataObjects.namePlural.endsWith('V2'))
.map((metadataObject) => {
return (
<NavItem
key={metadataObject.id}
label={capitalize(metadataObject.namePlural)}
to={`/objects/${metadataObject.namePlural}`}
Icon={metadataObject.icon ? icons[metadataObject.icon] : Icon123}
onClick={() => {
navigate(`/objects/${metadataObject.namePlural}`);
}}
/>
);
})}
</>
);
};

View File

@ -9,9 +9,9 @@ import { useMetadataObjectInContext } from '../hooks/useMetadataObjectInContext'
export const ObjectTableEffect = () => {
const {
setAvailableSorts,
setAvailableFilters,
setAvailableFields,
setAvailableSortDefinitions,
setAvailableFilterDefinitions,
setAvailableFieldDefinitions,
setViewType,
setViewObjectId,
} = useView();
@ -38,22 +38,22 @@ export const ObjectTableEffect = () => {
);
useEffect(() => {
setAvailableSorts?.([]); // TODO: extract from metadata fields
setAvailableFilters?.([]); // TODO: extract from metadata fields
setAvailableFields?.(columnDefinitions);
setAvailableSortDefinitions?.([]); // TODO: extract from metadata fields
setAvailableFilterDefinitions?.([]); // TODO: extract from metadata fields
setAvailableFieldDefinitions?.(columnDefinitions);
setViewObjectId?.(objectNamePlural);
setViewType?.(ViewType.Table);
setAvailableTableColumns(columnDefinitions);
}, [
setAvailableFields,
setAvailableFilters,
setAvailableSorts,
setAvailableTableColumns,
setViewObjectId,
setViewType,
columnDefinitions,
objectNamePlural,
setAvailableSortDefinitions,
setAvailableFilterDefinitions,
setAvailableFieldDefinitions,
]);
// useEffect(() => {

View File

@ -6,6 +6,7 @@ import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
import { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition';
import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation';
import { generateDeleteOneObjectMutation } from '../utils/generateDeleteOneObjectMutation';
import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery';
import { generateFindOneCustomObjectQuery } from '../utils/generateFindOneCustomObjectQuery';
import { generateUpdateOneObjectMutation } from '../utils/generateUpdateOneObjectMutation';
@ -79,7 +80,7 @@ export const useFindOneMetadataObject = ({
// TODO: implement backend delete
const deleteOneMutation = foundMetadataObject
? generateCreateOneObjectMutation({
? generateDeleteOneObjectMutation({
metadataObject: foundMetadataObject,
})
: gql`

View File

@ -7,7 +7,7 @@ import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scop
import { tableRowIdsState } from '@/ui/data/data-table/states/tableRowIdsState';
import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFamilyState';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { availableSortsScopedState } from '@/views/states/availableSortsScopedState';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
export const useSetObjectDataTableData = () => {
const resetTableRowSelection = useResetTableRowSelection();
@ -41,7 +41,10 @@ export const useSetObjectDataTableData = () => {
set(numberOfTableRowsState, entityIds.length);
set(availableSortsScopedState({ scopeId: tableContextScopeId }), []);
set(
availableSortDefinitionsScopedState({ scopeId: tableContextScopeId }),
[],
);
set(isFetchingDataTableDataState, false);
},

View File

@ -1,97 +0,0 @@
import styled from '@emotion/styled';
import { getPeopleOptimisticEffectDefinition } from '@/people/graphql/optimistic-effect-definitions/getPeopleOptimisticEffectDefinition';
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
import { DataTable } from '@/ui/data/data-table/components/DataTable';
import { DataTableEffect } from '@/ui/data/data-table/components/DataTableEffect';
import { TableContext } from '@/ui/data/data-table/contexts/TableContext';
import { useUpsertDataTableItem } from '@/ui/data/data-table/hooks/useUpsertDataTableItem';
import { TableOptionsDropdown } from '@/ui/data/data-table/options/components/TableOptionsDropdown';
import { ViewBar } from '@/views/components/ViewBar';
import { ViewBarEffect } from '@/views/components/ViewBarEffect';
import { ViewScope } from '@/views/scopes/ViewScope';
import {
UpdateOnePersonMutationVariables,
useGetPeopleQuery,
useUpdateOnePersonMutation,
} from '~/generated/graphql';
import { peopleAvailableFilters } from '~/pages/people/people-filters';
import { peopleAvailableSorts } from '~/pages/people/people-sorts';
import PersonTableEffect from './PersonTableEffect';
export const PeopleTable = () => {
const tableViewScopeId = 'person-table';
const [updateEntityMutation] = useUpdateOnePersonMutation();
const upsertDataTableItem = useUpsertDataTableItem();
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
const { setActionBarEntries } = usePersonTableActionBarEntries();
const { openPersonSpreadsheetImport: onImport } =
useSpreadsheetPersonImport();
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
return (
<ViewScope
viewScopeId={tableViewScopeId}
onViewFieldsChange={() => {}}
onViewSortsChange={() => {}}
onViewFiltersChange={() => {}}
>
<StyledContainer>
<TableContext.Provider
value={{
onColumnsChange: () => {},
}}
>
<ViewBarEffect />
<ViewBar
optionsDropdownButton={<TableOptionsDropdown onImport={onImport} />}
optionsDropdownScopeId="table-dropdown-option"
/>
<PersonTableEffect />
<DataTableEffect
getRequestResultKey="people"
useGetRequest={useGetPeopleQuery}
getRequestOptimisticEffectDefinition={
getPeopleOptimisticEffectDefinition
}
filterDefinitionArray={peopleAvailableFilters}
sortDefinitionArray={peopleAvailableSorts}
setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries}
/>
<DataTable
updateEntityMutation={({
variables,
}: {
variables: UpdateOnePersonMutationVariables;
}) =>
updateEntityMutation({
variables,
onCompleted: (data) => {
if (!data.updateOnePerson) {
return;
}
upsertDataTableItem(data.updateOnePerson);
},
})
}
/>
</TableContext.Provider>
</StyledContainer>
</ViewScope>
);
};

View File

@ -0,0 +1,135 @@
import styled from '@emotion/styled';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { peopleAvailableFieldDefinitions } from '@/people/constants/peopleAvailableFieldDefinitions';
import { getPeopleOptimisticEffectDefinition } from '@/people/graphql/optimistic-effect-definitions/getPeopleOptimisticEffectDefinition';
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePersonTableContextMenuEntries';
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
import { DataTable } from '@/ui/data/data-table/components/DataTable';
import { DataTableEffect } from '@/ui/data/data-table/components/DataTableEffect';
import { TableContext } from '@/ui/data/data-table/contexts/TableContext';
import { useUpsertDataTableItem } from '@/ui/data/data-table/hooks/useUpsertDataTableItem';
import { TableOptionsDropdown } from '@/ui/data/data-table/options/components/TableOptionsDropdown';
import { tableColumnsScopedState } from '@/ui/data/data-table/states/tableColumnsScopedState';
import { tableFiltersScopedState } from '@/ui/data/data-table/states/tableFiltersScopedState';
import { tableSortsScopedState } from '@/ui/data/data-table/states/tableSortsScopedState';
import { ViewBar } from '@/views/components/ViewBar';
import { useViewFields } from '@/views/hooks/internal/useViewFields';
import { useView } from '@/views/hooks/useView';
import { ViewScope } from '@/views/scopes/ViewScope';
import { columnDefinitionsToViewFields } from '@/views/utils/columnDefinitionToViewField';
import { viewFieldsToColumnDefinitions } from '@/views/utils/viewFieldsToColumnDefinitions';
import { viewFiltersToFilters } from '@/views/utils/viewFiltersToFilters';
import { viewSortsToSorts } from '@/views/utils/viewSortsToSorts';
import {
UpdateOnePersonMutationVariables,
useGetPeopleQuery,
useUpdateOnePersonMutation,
} from '~/generated/graphql';
import { personTableFilterDefinitions } from '~/pages/people/constants/personTableFilterDefinitions';
import { personTableSortDefinitions } from '~/pages/people/constants/personTableSortDefinitions';
import PersonTableEffect from './PersonTableEffect';
export const PersonTable = () => {
const viewScopeId = 'person-table-view';
const tableScopeId = 'people';
const setTableColumns = useSetRecoilState(
tableColumnsScopedState(tableScopeId),
);
const setTableFilters = useSetRecoilState(
tableFiltersScopedState(tableScopeId),
);
const setTableSorts = useSetRecoilState(tableSortsScopedState(tableScopeId));
const [updateEntityMutation] = useUpdateOnePersonMutation();
const upsertDataTableItem = useUpsertDataTableItem();
const { persistViewFields } = useViewFields(viewScopeId);
const { setCurrentViewFields } = useView({
viewScopeId,
});
const { setContextMenuEntries } = usePersonTableContextMenuEntries();
const { setActionBarEntries } = usePersonTableActionBarEntries();
const updatePerson = async (variables: UpdateOnePersonMutationVariables) => {
updateEntityMutation({
variables: variables,
onCompleted: (data) => {
if (!data.updateOnePerson) {
return;
}
upsertDataTableItem(data.updateOnePerson);
},
});
};
const { openPersonSpreadsheetImport: onImport } =
useSpreadsheetPersonImport();
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
return (
<ViewScope
viewScopeId={viewScopeId}
onViewFieldsChange={(viewFields) => {
setTableColumns(
viewFieldsToColumnDefinitions(
viewFields,
peopleAvailableFieldDefinitions,
),
);
}}
onViewFiltersChange={(viewFilters) => {
setTableFilters(viewFiltersToFilters(viewFilters));
}}
onViewSortsChange={(viewSorts) => {
setTableSorts(viewSortsToSorts(viewSorts));
}}
>
<StyledContainer>
<TableContext.Provider
value={{
onColumnsChange: useRecoilCallback(() => (columns) => {
setCurrentViewFields?.(columnDefinitionsToViewFields(columns));
persistViewFields(columnDefinitionsToViewFields(columns));
}),
}}
>
<ViewBar
optionsDropdownButton={<TableOptionsDropdown onImport={onImport} />}
optionsDropdownScopeId="table-dropdown-option"
/>
<PersonTableEffect />
<DataTableEffect
getRequestResultKey="people"
useGetRequest={useGetPeopleQuery}
getRequestOptimisticEffectDefinition={
getPeopleOptimisticEffectDefinition
}
filterDefinitionArray={personTableFilterDefinitions}
sortDefinitionArray={personTableSortDefinitions}
setContextMenuEntries={setContextMenuEntries}
setActionBarEntries={setActionBarEntries}
/>
<DataTable
updateEntityMutation={({
variables,
}: {
variables: UpdateOnePersonMutationVariables;
}) => updatePerson(variables)}
/>
</TableContext.Provider>
</StyledContainer>
</ViewScope>
);
};

View File

@ -7,14 +7,14 @@ import { tableColumnsScopedState } from '@/ui/data/data-table/states/tableColumn
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useView } from '@/views/hooks/useView';
import { ViewType } from '@/views/types/ViewType';
import { peopleAvailableFilters } from '~/pages/people/people-filters';
import { peopleAvailableSorts } from '~/pages/people/people-sorts';
import { personTableFilterDefinitions } from '~/pages/people/constants/personTableFilterDefinitions';
import { personTableSortDefinitions } from '~/pages/people/constants/personTableSortDefinitions';
const PeopleTableEffect = () => {
const {
setAvailableSorts,
setAvailableFilters,
setAvailableFields,
setAvailableSortDefinitions,
setAvailableFilterDefinitions,
setAvailableFieldDefinitions,
setViewType,
setViewObjectId,
} = useView();
@ -30,17 +30,17 @@ const PeopleTableEffect = () => {
);
useEffect(() => {
setAvailableSorts?.(peopleAvailableSorts);
setAvailableFilters?.(peopleAvailableFilters);
setAvailableFields?.(peopleAvailableFieldDefinitions);
setAvailableSortDefinitions?.(personTableSortDefinitions);
setAvailableFilterDefinitions?.(personTableFilterDefinitions);
setAvailableFieldDefinitions?.(peopleAvailableFieldDefinitions);
setViewObjectId?.('person');
setViewType?.(ViewType.Table);
setAvailableTableColumns(peopleAvailableFieldDefinitions);
}, [
setAvailableFields,
setAvailableFilters,
setAvailableSorts,
setAvailableFieldDefinitions,
setAvailableFilterDefinitions,
setAvailableSortDefinitions,
setAvailableTableColumns,
setTableColumns,
setViewObjectId,

View File

@ -1,3 +1,4 @@
import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
import {
FieldDateMetadata,
FieldMetadata,
@ -12,10 +13,9 @@ import {
IconUser,
} from '@/ui/display/icon';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { BoardFieldDefinition } from '@/ui/layout/board/types/BoardFieldDefinition';
import { Person } from '~/generated/graphql';
export const pipelineAvailableFieldDefinitions: BoardFieldDefinition<FieldMetadata>[] =
export const pipelineAvailableFieldDefinitions: ColumnDefinition<FieldMetadata>[] =
[
{
fieldId: 'closeDate',
@ -26,10 +26,11 @@ export const pipelineAvailableFieldDefinitions: BoardFieldDefinition<FieldMetada
metadata: {
fieldName: 'closeDate',
},
size: 0,
isVisible: true,
infoTooltipContent:
'Specified date by which an opportunity must be completed.',
} satisfies BoardFieldDefinition<FieldDateMetadata>,
} satisfies ColumnDefinition<FieldDateMetadata>,
{
fieldId: 'amount',
label: 'Amount',
@ -40,9 +41,10 @@ export const pipelineAvailableFieldDefinitions: BoardFieldDefinition<FieldMetada
fieldName: 'amount',
placeHolder: '0',
},
size: 0,
isVisible: true,
infoTooltipContent: 'Potential monetary value of a business opportunity.',
} satisfies BoardFieldDefinition<FieldNumberMetadata>,
} satisfies ColumnDefinition<FieldNumberMetadata>,
{
fieldId: 'probability',
label: 'Probability',
@ -52,10 +54,11 @@ export const pipelineAvailableFieldDefinitions: BoardFieldDefinition<FieldMetada
metadata: {
fieldName: 'probability',
},
size: 0,
isVisible: true,
infoTooltipContent:
"Level of certainty in the lead's potential to convert into a success.",
} satisfies BoardFieldDefinition<FieldProbabilityMetadata>,
} satisfies ColumnDefinition<FieldProbabilityMetadata>,
{
fieldId: 'pointOfContact',
label: 'Point of Contact',
@ -67,6 +70,7 @@ export const pipelineAvailableFieldDefinitions: BoardFieldDefinition<FieldMetada
relationType: Entity.Person,
useEditButton: true,
},
size: 0,
isVisible: true,
infoTooltipContent: 'Primary contact within the company.',
entityChipDisplayMapper: (dataObject: Person) => {
@ -76,5 +80,5 @@ export const pipelineAvailableFieldDefinitions: BoardFieldDefinition<FieldMetada
avatarType: 'rounded',
};
},
} satisfies BoardFieldDefinition<FieldRelationMetadata>,
} satisfies ColumnDefinition<FieldRelationMetadata>,
];

View File

@ -55,7 +55,12 @@ export const SettingsObjectIconSection = ({
<StyledArrowContainer>
<img src={ArrowRight} alt="Arrow right" width={32} height={16} />
</StyledArrowContainer>
<SettingsObjectIconWithLabel Icon={Icon} label={label || 'Investors'} />
{Icon && (
<SettingsObjectIconWithLabel
Icon={Icon}
label={label || 'Investors'}
/>
)}
</StyledContainer>
</Section>
);

View File

@ -168,37 +168,35 @@ export const DataTableHeader = () => {
>
<SelectAllCheckbox />
</th>
{[...visibleTableColumns]
.sort((columnA, columnB) => columnA.position - columnB.position)
.map((column) => (
<StyledColumnHeaderCell
key={column.fieldId}
isResizing={resizedFieldKey === column.fieldId}
columnWidth={Math.max(
tableColumnsByKey[column.fieldId].size +
(resizedFieldKey === column.fieldId ? resizeFieldOffset : 0),
COLUMN_MIN_WIDTH,
)}
>
<StyledColumnHeadContainer>
<ColumnHeadWithDropdown
column={column}
isFirstColumn={column.position === 1}
isLastColumn={
column.position === visibleTableColumns.length - 1
}
primaryColumnKey={primaryColumn?.fieldId || ''}
/>
</StyledColumnHeadContainer>
<StyledResizeHandler
className="cursor-col-resize"
role="separator"
onPointerDown={() => {
setResizedFieldKey(column.fieldId);
}}
{visibleTableColumns.map((column) => (
<StyledColumnHeaderCell
key={column.fieldId}
isResizing={resizedFieldKey === column.fieldId}
columnWidth={Math.max(
tableColumnsByKey[column.fieldId].size +
(resizedFieldKey === column.fieldId ? resizeFieldOffset : 0),
COLUMN_MIN_WIDTH,
)}
>
<StyledColumnHeadContainer>
<ColumnHeadWithDropdown
column={column}
isFirstColumn={column.position === 1}
isLastColumn={
column.position === visibleTableColumns.length - 1
}
primaryColumnKey={primaryColumn?.fieldId || ''}
/>
</StyledColumnHeaderCell>
))}
</StyledColumnHeadContainer>
<StyledResizeHandler
className="cursor-col-resize"
role="separator"
onPointerDown={() => {
setResizedFieldKey(column.fieldId);
}}
/>
</StyledColumnHeaderCell>
))}
<th>
{hiddenTableColumns.length > 0 && (
<StyledColumnHeadContainer>

View File

@ -4,7 +4,7 @@ import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFami
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { useView } from '@/views/hooks/useView';
import { availableSortsScopedState } from '@/views/states/availableSortsScopedState';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
import { SortDefinition } from '../../sort/types/SortDefinition';
import { isFetchingDataTableDataState } from '../states/isFetchingDataTableDataState';
@ -54,7 +54,7 @@ export const useSetDataTableData = () => {
setEntityCountInCurrentView(entityIds.length);
set(
availableSortsScopedState({ scopeId: tableContextScopeId }),
availableSortDefinitionsScopedState({ scopeId: tableContextScopeId }),
sortDefinitionArray,
);

View File

@ -13,7 +13,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { useViewGetStates } from '@/views/hooks/useViewGetStates';
import { useTableColumns } from '../../hooks/useTableColumns';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
@ -29,7 +29,7 @@ export const TableOptionsDropdownContent = ({
onImport?: () => void;
}) => {
const { setViewEditMode, handleViewNameSubmit } = useView();
const { viewEditMode, currentView } = useViewInternalStates();
const { viewEditMode, currentView } = useViewGetStates();
const { closeDropdown } = useDropdown();

View File

@ -12,12 +12,13 @@ type FilterDropdownButtonProps = {
export const FilterDropdownButton = ({
hotkeyScope,
}: FilterDropdownButtonProps) => {
const { availableFilters } = useFilter();
const { availableFilterDefinitions } = useFilter();
const hasOnlyOneEntityFilter =
availableFilters.length === 1 && availableFilters[0].type === 'entity';
availableFilterDefinitions.length === 1 &&
availableFilterDefinitions[0].type === 'entity';
if (!availableFilters.length) {
if (!availableFilterDefinitions.length) {
return <></>;
}

View File

@ -1,5 +1,4 @@
import { InternalDatePicker } from '@/ui/input/components/internal/date/components/InternalDatePicker';
import { useUpsertFilter } from '@/views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
@ -8,19 +7,18 @@ export const FilterDropdownDateSearchInput = () => {
filterDefinitionUsedInDropdown,
selectedOperandInDropdown,
setIsFilterDropdownUnfolded,
selectFilter,
} = useFilter();
const upsertFilter = useUpsertFilter();
const handleChange = (date: Date) => {
if (!filterDefinitionUsedInDropdown || !selectedOperandInDropdown) return;
upsertFilter({
key: filterDefinitionUsedInDropdown.key,
type: filterDefinitionUsedInDropdown.type,
selectFilter?.({
fieldId: filterDefinitionUsedInDropdown.fieldId,
value: date.toISOString(),
operand: selectedOperandInDropdown,
displayValue: date.toLocaleDateString(),
definition: filterDefinitionUsedInDropdown,
});
setIsFilterDropdownUnfolded(false);

View File

@ -1,11 +1,8 @@
import { useEffect, useState } from 'react';
import { useFilterCurrentlyEdited } from '@/ui/data/filter/hooks/useFilterCurrentlyEdited';
import { EntitiesForMultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { useRemoveFilter } from '@/views/hooks/useRemoveFilter';
import { useUpsertFilter } from '@/views/hooks/useUpsertFilter';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useFilter } from '../hooks/useFilter';
@ -16,20 +13,16 @@ export const FilterDropdownEntitySearchSelect = ({
entitiesForSelect: EntitiesForMultipleEntitySelect<EntityForSelect>;
}) => {
const {
filterDropdownSelectedEntityId,
setFilterDropdownSelectedEntityId,
filterDefinitionUsedInDropdown,
selectedOperandInDropdown,
filterDropdownSearchInput,
selectedFilter,
selectFilter,
} = useFilter();
const [isAllEntitySelected, setIsAllEntitySelected] = useState(false);
const upsertFilter = useUpsertFilter();
const removeFilter = useRemoveFilter();
const filterCurrentlyEdited = useFilterCurrentlyEdited();
const handleUserSelected = (
selectedEntity: EntityForSelect | null | undefined,
) => {
@ -45,24 +38,16 @@ export const FilterDropdownEntitySearchSelect = ({
setIsAllEntitySelected(false);
}
const clickedOnAlreadySelectedEntity =
selectedEntity.id === filterDropdownSelectedEntityId;
setFilterDropdownSelectedEntityId(selectedEntity.id);
if (clickedOnAlreadySelectedEntity) {
removeFilter(filterDefinitionUsedInDropdown.key);
setFilterDropdownSelectedEntityId(null);
} else {
setFilterDropdownSelectedEntityId(selectedEntity.id);
upsertFilter({
displayValue: selectedEntity.name,
key: filterDefinitionUsedInDropdown.key,
operand: selectedOperandInDropdown,
type: filterDefinitionUsedInDropdown.type,
value: selectedEntity.id,
displayAvatarUrl: selectedEntity.avatarUrl,
});
}
selectFilter?.({
displayValue: selectedEntity.name,
fieldId: filterDefinitionUsedInDropdown.fieldId,
operand: selectedOperandInDropdown,
value: selectedEntity.id,
displayAvatarUrl: selectedEntity.avatarUrl,
definition: filterDefinitionUsedInDropdown,
});
};
const isAllEntitySelectShown =
@ -81,36 +66,30 @@ export const FilterDropdownEntitySearchSelect = ({
) {
return;
}
if (isAllEntitySelected) {
setIsAllEntitySelected(false);
removeFilter(filterDefinitionUsedInDropdown.key);
} else {
setIsAllEntitySelected(true);
setIsAllEntitySelected(true);
setFilterDropdownSelectedEntityId(null);
setFilterDropdownSelectedEntityId(null);
upsertFilter({
displayValue: filterDefinitionUsedInDropdown.selectAllLabel,
key: filterDefinitionUsedInDropdown.key,
operand: ViewFilterOperand.IsNotNull,
type: filterDefinitionUsedInDropdown.type,
value: '',
});
}
selectFilter?.({
displayValue: filterDefinitionUsedInDropdown.selectAllLabel,
fieldId: filterDefinitionUsedInDropdown.fieldId,
operand: ViewFilterOperand.IsNotNull,
value: '',
definition: filterDefinitionUsedInDropdown,
});
};
useEffect(() => {
if (!filterCurrentlyEdited) {
if (!selectedFilter) {
setFilterDropdownSelectedEntityId(null);
} else {
setFilterDropdownSelectedEntityId(filterCurrentlyEdited.value);
setFilterDropdownSelectedEntityId(selectedFilter.value);
setIsAllEntitySelected(
filterCurrentlyEdited.operand === ViewFilterOperand.IsNotNull,
selectedFilter.operand === ViewFilterOperand.IsNotNull,
);
}
}, [
filterCurrentlyEdited,
selectedFilter,
setFilterDropdownSelectedEntityId,
entitiesForSelect.selectedEntities,
]);

View File

@ -11,32 +11,32 @@ export const FilterDropdownFilterSelect = () => {
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
setFilterDropdownSearchInput,
availableFilters,
availableFilterDefinitions,
} = useFilter();
const setHotkeyScope = useSetHotkeyScope();
return (
<DropdownMenuItemsContainer>
{availableFilters.map((availableFilter, index) => (
{availableFilterDefinitions.map((availableFilterDefinition, index) => (
<MenuItem
key={`select-filter-${index}`}
testId={`select-filter-${index}`}
onClick={() => {
setFilterDefinitionUsedInDropdown(availableFilter);
setFilterDefinitionUsedInDropdown(availableFilterDefinition);
if (availableFilter.type === 'entity') {
if (availableFilterDefinition.type === 'entity') {
setHotkeyScope(RelationPickerHotkeyScope.RelationPicker);
}
setSelectedOperandInDropdown(
getOperandsForFilterType(availableFilter.type)?.[0],
getOperandsForFilterType(availableFilterDefinition.type)?.[0],
);
setFilterDropdownSearchInput('');
}}
LeftIcon={availableFilter.Icon}
text={availableFilter.label}
LeftIcon={availableFilterDefinition.Icon}
text={availableFilterDefinition.label}
/>
))}
</DropdownMenuItemsContainer>

View File

@ -2,16 +2,14 @@ import { ChangeEvent } from 'react';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { useRemoveFilter } from '../../../../views/hooks/useRemoveFilter';
import { useUpsertFilter } from '../../../../views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
export const FilterDropdownNumberSearchInput = () => {
const { selectedOperandInDropdown, filterDefinitionUsedInDropdown } =
useFilter();
const upsertFilter = useUpsertFilter();
const removeFilter = useRemoveFilter();
const {
selectedOperandInDropdown,
filterDefinitionUsedInDropdown,
selectFilter,
} = useFilter();
return (
filterDefinitionUsedInDropdown &&
@ -21,17 +19,13 @@ export const FilterDropdownNumberSearchInput = () => {
type="number"
placeholder={filterDefinitionUsedInDropdown.label}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value === '') {
removeFilter(filterDefinitionUsedInDropdown.key);
} else {
upsertFilter({
key: filterDefinitionUsedInDropdown.key,
type: filterDefinitionUsedInDropdown.type,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
});
}
selectFilter?.({
fieldId: filterDefinitionUsedInDropdown.fieldId,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
definition: filterDefinitionUsedInDropdown,
});
}}
/>
)

View File

@ -2,9 +2,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { useUpsertFilter } from '../../../../views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
import { getOperandLabel } from '../utils/getOperandLabel';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
@ -14,27 +12,25 @@ export const FilterDropdownOperandSelect = () => {
setSelectedOperandInDropdown,
isFilterDropdownOperandSelectUnfolded,
setIsFilterDropdownOperandSelectUnfolded,
selectedFilter,
selectFilter,
} = useFilter();
const operandsForFilterType = getOperandsForFilterType(
filterDefinitionUsedInDropdown?.type,
);
const filterCurrentlyEdited = useFilterCurrentlyEdited();
const upsertFilter = useUpsertFilter();
const handleOperangeChange = (newOperand: ViewFilterOperand) => {
setSelectedOperandInDropdown(newOperand);
setIsFilterDropdownOperandSelectUnfolded(false);
if (filterDefinitionUsedInDropdown && filterCurrentlyEdited) {
upsertFilter({
key: filterCurrentlyEdited.key,
displayValue: filterCurrentlyEdited.displayValue,
if (filterDefinitionUsedInDropdown && selectedFilter) {
selectFilter?.({
fieldId: selectedFilter.fieldId,
displayValue: selectedFilter.displayValue,
operand: newOperand,
type: filterCurrentlyEdited.type,
value: filterCurrentlyEdited.value,
value: selectedFilter.value,
definition: filterDefinitionUsedInDropdown,
});
}
};

View File

@ -2,10 +2,7 @@ import { ChangeEvent } from 'react';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { useRemoveFilter } from '../../../../views/hooks/useRemoveFilter';
import { useUpsertFilter } from '../../../../views/hooks/useUpsertFilter';
import { useFilter } from '../hooks/useFilter';
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
export const FilterDropdownTextSearchInput = () => {
const {
@ -13,13 +10,10 @@ export const FilterDropdownTextSearchInput = () => {
selectedOperandInDropdown,
filterDropdownSearchInput,
setFilterDropdownSearchInput,
selectedFilter,
selectFilter,
} = useFilter();
const upsertFilter = useUpsertFilter();
const removeFilter = useRemoveFilter();
const filterCurrentlyEdited = useFilterCurrentlyEdited();
return (
filterDefinitionUsedInDropdown &&
selectedOperandInDropdown && (
@ -27,21 +21,17 @@ export const FilterDropdownTextSearchInput = () => {
autoFocus
type="text"
placeholder={filterDefinitionUsedInDropdown.label}
value={filterCurrentlyEdited?.value ?? filterDropdownSearchInput}
value={selectedFilter?.value ?? filterDropdownSearchInput}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setFilterDropdownSearchInput(event.target.value);
if (event.target.value === '') {
removeFilter(filterDefinitionUsedInDropdown.key);
} else {
upsertFilter({
key: filterDefinitionUsedInDropdown.key,
type: filterDefinitionUsedInDropdown.type,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
});
}
selectFilter?.({
fieldId: filterDefinitionUsedInDropdown.fieldId,
value: event.target.value,
operand: selectedOperandInDropdown,
displayValue: event.target.value,
definition: filterDefinitionUsedInDropdown,
});
}}
/>
)

View File

@ -21,13 +21,13 @@ export const SingleEntityFilterDropdownButton = ({
hotkeyScope: HotkeyScope;
}) => {
const {
availableFilters,
selectedFilters,
availableFilterDefinitions,
selectedFilter,
setFilterDefinitionUsedInDropdown,
setSelectedOperandInDropdown,
} = useFilter();
const availableFilter = availableFilters[0];
const availableFilter = availableFilterDefinitions[0];
React.useEffect(() => {
setFilterDefinitionUsedInDropdown(availableFilter);
@ -48,11 +48,11 @@ export const SingleEntityFilterDropdownButton = ({
dropdownOffset={{ x: 0, y: -28 }}
clickableComponent={
<StyledHeaderDropdownButton>
{selectedFilters[0] ? (
{selectedFilter ? (
<GenericEntityFilterChip
filter={selectedFilters[0]}
filter={selectedFilter}
Icon={
selectedFilters[0].operand === ViewFilterOperand.IsNotNull
selectedFilter.operand === ViewFilterOperand.IsNotNull
? availableFilter.SelectAllIcon
: undefined
}

View File

@ -1,6 +1,10 @@
import { useCallback } from 'react';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { useScopeInternalContextOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopeInternalContextOrThrow';
import { FilterScopeInternalContext } from '../scopes/scope-internal-context/FilterScopeInternalContext';
import { Filter } from '../types/Filter';
import { useFilterStates } from './useFilterStates';
@ -14,8 +18,8 @@ export const useFilter = (props?: UseFilterProps) => {
props?.filterScopeId,
);
const {
availableFilters,
setAvailableFilters,
availableFilterDefinitions,
setAvailableFilterDefinitions,
filterDefinitionUsedInDropdown,
setFilterDefinitionUsedInDropdown,
filterDropdownSearchInput,
@ -26,16 +30,28 @@ export const useFilter = (props?: UseFilterProps) => {
setIsFilterDropdownOperandSelectUnfolded,
isFilterDropdownUnfolded,
setIsFilterDropdownUnfolded,
selectedFilters,
setSelectedFilters,
selectedFilter,
setSelectedFilter,
selectedOperandInDropdown,
setSelectedOperandInDropdown,
} = useFilterStates(scopeId);
const { onFilterSelect } = useScopeInternalContextOrThrow(
FilterScopeInternalContext,
);
const selectFilter = useCallback(
(filter: Filter) => {
setSelectedFilter(filter);
onFilterSelect?.(filter);
},
[setSelectedFilter, onFilterSelect],
);
return {
scopeId,
availableFilters,
setAvailableFilters,
availableFilterDefinitions,
setAvailableFilterDefinitions,
filterDefinitionUsedInDropdown,
setFilterDefinitionUsedInDropdown,
filterDropdownSearchInput,
@ -46,9 +62,10 @@ export const useFilter = (props?: UseFilterProps) => {
setIsFilterDropdownOperandSelectUnfolded,
isFilterDropdownUnfolded,
setIsFilterDropdownUnfolded,
selectedFilters,
setSelectedFilters,
selectedFilter,
setSelectedFilter,
selectedOperandInDropdown,
setSelectedOperandInDropdown,
selectFilter,
};
};

View File

@ -1,13 +0,0 @@
import { useMemo } from 'react';
import { useFilter } from './useFilter';
export const useFilterCurrentlyEdited = () => {
const { selectedFilters, filterDefinitionUsedInDropdown } = useFilter();
return useMemo(() => {
return selectedFilters?.find(
(filter) => filter.key === filterDefinitionUsedInDropdown?.key,
);
}, [filterDefinitionUsedInDropdown?.key, selectedFilters]);
};

View File

@ -1,19 +1,17 @@
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { availableFilterDefinitionsScopedState } from '../states/availableFilterDefinitionsScopedState';
import { filterDefinitionUsedInDropdownScopedState } from '../states/filterDefinitionUsedInDropdownScopedState';
import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSearchInputScopedState';
import { filterDropdownSelectedEntityIdScopedState } from '../states/filterDropdownSelectedEntityIdScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { isFilterDropdownUnfoldedScopedState } from '../states/isFilterDropdownUnfoldedScopedState';
import { selectedFiltersScopedState } from '../states/selectedFiltersScopedState';
import { selectedFilterScopedState } from '../states/selectedFilterScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
export const useFilterStates = (scopeId: string) => {
const [availableFilters, setAvailableFilters] = useRecoilScopedStateV2(
availableFiltersScopedState,
scopeId,
);
const [availableFilterDefinitions, setAvailableFilterDefinitions] =
useRecoilScopedStateV2(availableFilterDefinitionsScopedState, scopeId);
const [filterDefinitionUsedInDropdown, setFilterDefinitionUsedInDropdown] =
useRecoilScopedStateV2(filterDefinitionUsedInDropdownScopedState, scopeId);
@ -35,8 +33,8 @@ export const useFilterStates = (scopeId: string) => {
const [isFilterDropdownUnfolded, setIsFilterDropdownUnfolded] =
useRecoilScopedStateV2(isFilterDropdownUnfoldedScopedState, scopeId);
const [selectedFilters, setSelectedFilters] = useRecoilScopedStateV2(
selectedFiltersScopedState,
const [selectedFilter, setSelectedFilter] = useRecoilScopedStateV2(
selectedFilterScopedState,
scopeId,
);
@ -44,8 +42,8 @@ export const useFilterStates = (scopeId: string) => {
useRecoilScopedStateV2(selectedOperandInDropdownScopedState, scopeId);
return {
availableFilters,
setAvailableFilters,
availableFilterDefinitions,
setAvailableFilterDefinitions,
filterDefinitionUsedInDropdown,
setFilterDefinitionUsedInDropdown,
filterDropdownSearchInput,
@ -56,8 +54,8 @@ export const useFilterStates = (scopeId: string) => {
setIsFilterDropdownOperandSelectUnfolded,
isFilterDropdownUnfolded,
setIsFilterDropdownUnfolded,
selectedFilters,
setSelectedFilters,
selectedFilter,
setSelectedFilter,
selectedOperandInDropdown,
setSelectedOperandInDropdown,
};

View File

@ -2,25 +2,31 @@ import { ReactNode } from 'react';
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { Filter } from '../types/Filter';
import { FilterScopeInitEffect } from './init-effect/FilterScopeInitEffect';
import { FilterScopeInternalContext } from './scope-internal-context/FilterScopeInternalContext';
type FilterScopeProps = {
children: ReactNode;
filterScopeId: string;
availableFilters?: FilterDefinition[];
availableFilterDefinitions?: FilterDefinition[];
onFilterSelect?: (filter: Filter) => void;
};
export const FilterScope = ({
children,
filterScopeId,
availableFilters,
availableFilterDefinitions,
onFilterSelect,
}: FilterScopeProps) => {
return (
<FilterScopeInternalContext.Provider value={{ scopeId: filterScopeId }}>
<FilterScopeInternalContext.Provider
value={{ scopeId: filterScopeId, onFilterSelect }}
>
<FilterScopeInitEffect
filterScopeId={filterScopeId}
availableFilters={availableFilters}
availableFilterDefinitions={availableFilterDefinitions}
/>
{children}
</FilterScopeInternalContext.Provider>

View File

@ -6,20 +6,20 @@ import { useFilterStates } from '../../hooks/useFilterStates';
type FilterScopeInitEffectProps = {
filterScopeId: string;
availableFilters?: FilterDefinition[];
availableFilterDefinitions?: FilterDefinition[];
};
export const FilterScopeInitEffect = ({
filterScopeId,
availableFilters,
availableFilterDefinitions,
}: FilterScopeInitEffectProps) => {
const { setAvailableFilters } = useFilterStates(filterScopeId);
const { setAvailableFilterDefinitions } = useFilterStates(filterScopeId);
useEffect(() => {
if (availableFilters) {
setAvailableFilters(availableFilters);
if (availableFilterDefinitions) {
setAvailableFilterDefinitions(availableFilterDefinitions);
}
}, [availableFilters, setAvailableFilters]);
}, [availableFilterDefinitions, setAvailableFilterDefinitions]);
return <></>;
};

View File

@ -1,8 +1,10 @@
import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopedStateKey';
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { Filter } from '../../types/Filter';
type FilterScopeInternalContextProps = ScopedStateKey & {
test?: string;
onFilterSelect?: (sort: Filter) => void;
};
export const FilterScopeInternalContext =

View File

@ -1,9 +1,9 @@
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const availableFiltersScopedState = createScopedState<
export const availableFilterDefinitionsScopedState = createScopedState<
FilterDefinition[]
>({
key: 'availableFiltersScopedState',
key: 'availableFilterDefinitionsScopedState',
defaultValue: [],
});

View File

@ -0,0 +1,8 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { Filter } from '../types/Filter';
export const selectedFilterScopedState = createScopedState<Filter | undefined>({
key: 'selectedFilterScopedState',
defaultValue: undefined,
});

View File

@ -1,8 +0,0 @@
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
import { Filter } from '../types/Filter';
export const selectedFiltersScopedState = createScopedState<Filter[]>({
key: 'selectedFiltersScopedState',
defaultValue: [],
});

View File

@ -1,12 +1,12 @@
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { FilterType } from './FilterType';
import { FilterDefinition } from './FilterDefinition';
export type Filter = {
key: string;
type: FilterType;
fieldId: string;
value: string;
displayValue: string;
displayAvatarUrl?: string;
operand: ViewFilterOperand;
definition: FilterDefinition;
};

View File

@ -3,7 +3,7 @@ import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { FilterType } from './FilterType';
export type FilterDefinition = {
key: string;
fieldId: string;
label: string;
Icon: IconComponent;
type: FilterType;

View File

@ -1,5 +1,5 @@
import { FilterDefinition } from './FilterDefinition';
export type FilterDefinitionByEntity<T> = FilterDefinition & {
key: keyof T;
fieldId: keyof T;
};

View File

@ -3,28 +3,39 @@ import { QueryMode } from '~/generated/graphql';
import { Filter } from '../types/Filter';
export const turnFilterIntoWhereClause = (filter: Filter) => {
type FilterToTurnIntoWhereClause = Omit<Filter, 'definition'> & {
definition: {
type: Filter['definition']['type'];
};
};
export const turnFilterIntoWhereClause = (
filter: FilterToTurnIntoWhereClause | undefined,
) => {
if (!filter) {
return {};
}
switch (filter.operand) {
case ViewFilterOperand.IsNotNull:
return {
[filter.key]: {
[filter.fieldId]: {
not: null,
},
};
default:
switch (filter.type) {
switch (filter.definition.type) {
case 'text':
switch (filter.operand) {
case ViewFilterOperand.Contains:
return {
[filter.key]: {
[filter.fieldId]: {
contains: filter.value,
mode: QueryMode.Insensitive,
},
};
case ViewFilterOperand.DoesNotContain:
return {
[filter.key]: {
[filter.fieldId]: {
not: {
contains: filter.value,
mode: QueryMode.Insensitive,
@ -33,64 +44,64 @@ export const turnFilterIntoWhereClause = (filter: Filter) => {
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
case 'number':
switch (filter.operand) {
case ViewFilterOperand.GreaterThan:
return {
[filter.key]: {
[filter.fieldId]: {
gte: parseFloat(filter.value),
},
};
case ViewFilterOperand.LessThan:
return {
[filter.key]: {
[filter.fieldId]: {
lte: parseFloat(filter.value),
},
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
case 'date':
switch (filter.operand) {
case ViewFilterOperand.GreaterThan:
return {
[filter.key]: {
[filter.fieldId]: {
gte: filter.value,
},
};
case ViewFilterOperand.LessThan:
return {
[filter.key]: {
[filter.fieldId]: {
lte: filter.value,
},
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
case 'entity':
switch (filter.operand) {
case ViewFilterOperand.Is:
return {
[filter.key]: {
[filter.fieldId]: {
equals: filter.value,
},
};
case ViewFilterOperand.IsNot:
return {
[filter.key]: {
[filter.fieldId]: {
not: { equals: filter.value },
},
};
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.type} filter`,
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
default:

View File

@ -35,7 +35,7 @@ export const SortDropdownButton = ({
setSelectedSortDirection('asc');
}, []);
const { availableSorts, onSortAdd, isSortSelected } = useSort();
const { availableSortDefinitions, onSortSelect, isSortSelected } = useSort();
const { toggleDropdown } = useDropdown({
dropdownScopeId: SortDropdownId,
@ -48,8 +48,8 @@ export const SortDropdownButton = ({
const handleAddSort = (selectedSortDefinition: SortDefinition) => {
toggleDropdown();
onSortAdd?.({
key: selectedSortDefinition.key,
onSortSelect?.({
fieldId: selectedSortDefinition.fieldId,
direction: selectedSortDirection,
definition: selectedSortDefinition,
});
@ -96,7 +96,7 @@ export const SortDropdownButton = ({
</DropdownMenuHeader>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
{availableSorts.map((availableSort, index) => (
{availableSortDefinitions.map((availableSort, index) => (
<MenuItem
testId={`select-sort-${index}`}
key={index}

View File

@ -15,22 +15,22 @@ export const useSort = (props?: UseSortProps) => {
props?.sortScopeId,
);
const {
availableSorts,
setAvailableSorts,
availableSortDefinitions,
setAvailableSortDefinitions,
isSortSelected,
setIsSortSelected,
} = useSortStates(scopeId);
const { onSortAdd } = useScopeInternalContextOrThrow(
const { onSortSelect } = useScopeInternalContextOrThrow(
SortScopeInternalContext,
);
return {
onSortAdd,
onSortSelect,
scopeId,
availableSorts,
availableSortDefinitions,
isSortSelected,
setIsSortSelected,
setAvailableSorts,
setAvailableSortDefinitions,
};
};

View File

@ -1,13 +1,11 @@
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
import { availableSortsScopedState } from '@/views/states/availableSortsScopedState';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
import { isSortSelectedScopedState } from '../states/isSortSelectedScopedState';
export const useSortStates = (scopeId: string) => {
const [availableSorts, setAvailableSorts] = useRecoilScopedStateV2(
availableSortsScopedState,
scopeId,
);
const [availableSortDefinitions, setAvailableSortDefinitions] =
useRecoilScopedStateV2(availableSortDefinitionsScopedState, scopeId);
const [isSortSelected, setIsSortSelected] = useRecoilScopedStateV2(
isSortSelectedScopedState,
@ -15,8 +13,8 @@ export const useSortStates = (scopeId: string) => {
);
return {
availableSorts,
setAvailableSorts,
availableSortDefinitions,
setAvailableSortDefinitions,
isSortSelected,
setIsSortSelected,
};

View File

@ -9,23 +9,23 @@ import { SortScopeInternalContext } from './scope-internal-context/SortScopeInte
type SortScopeProps = {
children: ReactNode;
sortScopeId: string;
availableSorts?: SortDefinition[];
onSortAdd?: (sort: Sort) => void | Promise<void>;
availableSortDefinitions?: SortDefinition[];
onSortSelect?: (sort: Sort) => void | Promise<void>;
};
export const SortScope = ({
children,
sortScopeId,
availableSorts,
onSortAdd,
availableSortDefinitions,
onSortSelect,
}: SortScopeProps) => {
return (
<SortScopeInternalContext.Provider
value={{ scopeId: sortScopeId, onSortAdd }}
value={{ scopeId: sortScopeId, onSortSelect }}
>
<SortScopeInitEffect
sortScopeId={sortScopeId}
availableSorts={availableSorts}
availableSortDefinitions={availableSortDefinitions}
/>
{children}
</SortScopeInternalContext.Provider>

View File

@ -6,20 +6,20 @@ import { useSortStates } from '../../hooks/useSortStates';
type SortScopeInitEffectProps = {
sortScopeId: string;
availableSorts?: SortDefinition[];
availableSortDefinitions?: SortDefinition[];
};
export const SortScopeInitEffect = ({
sortScopeId,
availableSorts,
availableSortDefinitions,
}: SortScopeInitEffectProps) => {
const { setAvailableSorts } = useSortStates(sortScopeId);
const { setAvailableSortDefinitions } = useSortStates(sortScopeId);
useEffect(() => {
if (availableSorts) {
setAvailableSorts(availableSorts);
if (availableSortDefinitions) {
setAvailableSortDefinitions(availableSortDefinitions);
}
}, [availableSorts, setAvailableSorts]);
}, [availableSortDefinitions, setAvailableSortDefinitions]);
return <></>;
};

View File

@ -2,11 +2,9 @@ import { ScopedStateKey } from '@/ui/utilities/recoil-scope/scopes-internal/type
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
import { Sort } from '../../types/Sort';
import { SortDefinition } from '../../types/SortDefinition';
type SortScopeInternalContextProps = ScopedStateKey & {
onSortAdd?: (sort: Sort) => void;
availableSorts?: SortDefinition[];
onSortSelect?: (sort: Sort) => void;
};
export const SortScopeInternalContext =

View File

@ -2,7 +2,7 @@ import { SortDefinition } from './SortDefinition';
import { SortDirection } from './SortDirection';
export type Sort = {
key: string;
fieldId: string;
direction: SortDirection;
definition: SortDefinition;
};

View File

@ -3,7 +3,7 @@ import { IconComponent } from '@/ui/display/icon/types/IconComponent';
import { SortDirection } from './SortDirection';
export type SortDefinition = {
key: string;
fieldId: string;
label: string;
Icon?: IconComponent;
getOrderByTemplate?: (direction: SortDirection) => any[];

View File

@ -10,7 +10,7 @@ export const reduceSortsToOrderBy = (sorts: Sort[]): any[] =>
if (sort.definition.getOrderByTemplate) {
return sort.definition.getOrderByTemplate(direction);
} else {
return [{ [sort.definition.key]: direction }];
return [{ [sort.definition.fieldId]: direction }];
}
})
.flat();

View File

@ -2,8 +2,9 @@ import { useView } from '@/views/hooks/useView';
import { Dropdown } from '../../dropdown/components/Dropdown';
import { DropdownScope } from '../../dropdown/scopes/DropdownScope';
import { BoardScopeIds } from '../types/enums/BoardScopeIds';
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
import { BoardOptionsDropdownId } from './constants/BoardOptionsDropdownId';
import { BoardOptionsDropdownButton } from './BoardOptionsDropdownButton';
import {
BoardOptionsDropdownContent,
@ -12,26 +13,22 @@ import {
type BoardOptionsDropdownProps = Pick<
BoardOptionsDropdownContentProps,
'customHotkeyScope' | 'onStageAdd'
'onStageAdd'
>;
export const BoardOptionsDropdown = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownProps) => {
const { setViewEditMode } = useView();
return (
<DropdownScope dropdownScopeId={BoardScopeIds.OptionsDropdown}>
<DropdownScope dropdownScopeId={BoardOptionsDropdownId}>
<Dropdown
clickableComponent={<BoardOptionsDropdownButton />}
dropdownComponents={
<BoardOptionsDropdownContent
customHotkeyScope={customHotkeyScope}
onStageAdd={onStageAdd}
/>
<BoardOptionsDropdownContent onStageAdd={onStageAdd} />
}
dropdownHotkeyScope={customHotkeyScope}
dropdownHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
onClickOutside={() => setViewEditMode('none')}
dropdownMenuWidth={170}
/>

View File

@ -1,12 +1,8 @@
import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/StyledHeaderDropdownButton';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { BoardScopeIds } from '../types/enums/BoardScopeIds';
export const BoardOptionsDropdownButton = () => {
const { isDropdownOpen, toggleDropdown } = useDropdown({
dropdownScopeId: BoardScopeIds.OptionsDropdown,
});
const { isDropdownOpen, toggleDropdown } = useDropdown();
const handleClick = () => {
toggleDropdown();

View File

@ -1,5 +1,5 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
@ -22,25 +22,20 @@ import { MenuItemNavigate } from '@/ui/navigation/menu-item/components/MenuItemN
import { MenuItemToggle } from '@/ui/navigation/menu-item/components/MenuItemToggle';
import { ThemeColor } from '@/ui/theme/constants/colors';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { ViewFieldsVisibilityDropdownSection } from '@/views/components/ViewFieldsVisibilityDropdownSection';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { viewEditModeScopedState } from '@/views/states/viewEditModeScopedState';
import { useViewGetStates } from '@/views/hooks/useViewGetStates';
import { useBoardCardFields } from '../hooks/useBoardCardFields';
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
import { boardColumnsState } from '../states/boardColumnsState';
import { isCompactViewEnabledState } from '../states/isCompactViewEnabledState';
import { savedBoardCardFieldsFamilyState } from '../states/savedBoardCardFieldsFamilyState';
import { hiddenBoardCardFieldsScopedSelector } from '../states/selectors/hiddenBoardCardFieldsScopedSelector';
import { visibleBoardCardFieldsScopedSelector } from '../states/selectors/visibleBoardCardFieldsScopedSelector';
import { BoardColumnDefinition } from '../types/BoardColumnDefinition';
import { BoardOptionsHotkeyScope } from '../types/BoardOptionsHotkeyScope';
export type BoardOptionsDropdownContentProps = {
customHotkeyScope: HotkeyScope;
onStageAdd?: (boardColumn: BoardColumnDefinition) => void;
};
@ -54,15 +49,12 @@ type ColumnForCreate = {
};
export const BoardOptionsDropdownContent = ({
customHotkeyScope,
onStageAdd,
}: BoardOptionsDropdownContentProps) => {
const { setViewEditMode, createView, currentViewId } = useView();
const { viewEditMode, currentView } = useViewInternalStates();
const { setViewEditMode, handleViewNameSubmit } = useView();
const { viewEditMode, currentView } = useViewGetStates();
const { BoardRecoilScopeContext } = useContext(BoardContext);
const boardRecoilScopeId = useRecoilScopeId(BoardRecoilScopeContext);
const stageInputRef = useRef<HTMLInputElement>(null);
const viewEditInputRef = useRef<HTMLInputElement>(null);
@ -104,31 +96,6 @@ export const BoardOptionsDropdownContent = ({
onStageAdd?.(columnToCreate);
};
const handleViewNameSubmit = useRecoilCallback(
({ set, snapshot }) =>
async () => {
const viewEditMode = snapshot
.getLoadable(viewEditModeScopedState({ scopeId: boardRecoilScopeId }))
.getValue();
if (!viewEditMode) {
return;
}
const boardCardFields = await snapshot.getPromise(
boardCardFieldsScopedState(boardRecoilScopeId),
);
const isCreateMode = viewEditMode === 'create';
const name = viewEditInputRef.current?.value;
if (isCreateMode && name) {
await createView(name);
set(savedBoardCardFieldsFamilyState(currentViewId), boardCardFields);
}
},
[boardRecoilScopeId, createView, currentViewId],
);
const resetMenu = () => setCurrentMenu(undefined);
const handleMenuNavigate = (menu: BoardOptionsMenu) => {
@ -146,43 +113,39 @@ export const BoardOptionsDropdownContent = ({
setViewEditMode('none');
closeDropdown();
},
customHotkeyScope.scope,
BoardOptionsHotkeyScope.Dropdown,
);
useScopedHotkeys(
Key.Enter,
() => {
const name = viewEditInputRef.current?.value;
resetMenu();
setViewEditMode('none');
closeDropdown();
handleStageSubmit();
handleViewNameSubmit();
handleViewNameSubmit(name);
closeDropdown();
},
customHotkeyScope.scope,
BoardOptionsHotkeyScope.Dropdown,
);
return (
<>
{!currentMenu && (
<>
{viewEditMode && (
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode !== 'none'}
placeholder={
viewEditMode === 'create'
? 'New view'
: viewEditMode === 'edit'
? 'View name'
: ''
}
defaultValue={
viewEditMode === 'create'
? ''
: viewEditMode === 'edit'
? currentView?.name
: ''
}
/>
)}
<DropdownMenuInput
ref={viewEditInputRef}
autoFocus={viewEditMode !== 'none'}
placeholder={
viewEditMode === 'create'
? 'New view'
: viewEditMode === 'edit'
? 'View name'
: ''
}
defaultValue={viewEditMode === 'create' ? '' : currentView?.name}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer>
<MenuItemNavigate

View File

@ -40,6 +40,8 @@ export type EntityBoardProps = {
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
width: 100%;
`;

View File

@ -0,0 +1,2 @@
// We should either apply the constant all caps case or maybe define a more general enum to store those ids ?
export const BoardOptionsDropdownId = 'board-options';

View File

@ -7,6 +7,6 @@ import { PipelineProgress } from '~/generated/graphql';
export type BoardOptions = {
newCardComponent: React.ReactNode;
CardComponent: ComponentType;
filters: FilterDefinitionByEntity<PipelineProgress>[];
sorts: SortDefinition[];
filterDefinitions: FilterDefinitionByEntity<PipelineProgress>[];
sortDefinitions: SortDefinition[];
};

View File

@ -1,3 +0,0 @@
export enum BoardScopeIds {
OptionsDropdown = 'board-options',
}

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();

View File

@ -8,9 +8,10 @@ import { viewObjectIdScopeState } from '@/views/states/viewObjectIdScopeState';
import { ViewField } from '@/views/types/ViewField';
export const useViewFields = (viewScopeId: string) => {
const { updateOneMutation, createOneMutation } = useFindOneMetadataObject({
objectNameSingular: 'viewFieldV2',
});
const { updateOneMutation, createOneMutation, findManyQuery } =
useFindOneMetadataObject({
objectNameSingular: 'viewFieldV2',
});
const apolloClient = useApolloClient();
const persistViewFields = useRecoilCallback(
@ -49,12 +50,13 @@ export const useViewFields = (viewScopeId: string) => {
variables: {
input: {
fieldId: viewField.fieldId,
viewId: currentViewId,
viewId: viewId,
isVisible: viewField.isVisible,
size: viewField.size,
position: viewField.position,
},
},
refetchQueries: [findManyQuery],
}),
),
);

View File

@ -1,14 +1,26 @@
import { useApolloClient } from '@apollo/client';
import { produce } from 'immer';
import { useRecoilCallback } from 'recoil';
import { useFindOneMetadataObject } from '@/metadata/hooks/useFindOneMetadataObject';
import { Filter } from '@/ui/data/filter/types/Filter';
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { availableFiltersScopedState } from '@/views/states/availableFiltersScopedState';
import { currentViewFiltersScopedFamilyState } from '@/views/states/currentViewFiltersScopedFamilyState';
import { currentViewIdScopedState } from '@/views/states/currentViewIdScopedState';
import { onViewFiltersChangeScopedState } from '@/views/states/onViewFiltersChangeScopedState';
import { savedViewFiltersScopedFamilyState } from '@/views/states/savedViewFiltersScopedFamilyState';
import { savedViewFiltersByKeyScopedFamilySelector } from '@/views/states/selectors/savedViewFiltersByKeyScopedFamilySelector';
import { ViewFilter } from '@/views/types/ViewFilter';
import { useViewSetStates } from '../useViewSetStates';
export const useViewFilters = (viewScopeId: string) => {
const { updateOneMutation, createOneMutation, findManyQuery } =
useFindOneMetadataObject({
objectNameSingular: 'viewFilterV2',
});
const apolloClient = useApolloClient();
const { setCurrentViewFilters } = useViewSetStates(viewScopeId);
const persistViewFilters = useRecoilCallback(
({ snapshot, set }) =>
async (viewId?: string) => {
@ -19,69 +31,52 @@ export const useViewFilters = (viewScopeId: string) => {
return;
}
const _createViewFilters = (
filters: Filter[],
availableFilters: FilterDefinition[] = [],
) => {
if (!currentViewId || !filters.length) {
return;
}
const createViewFilters = (viewFiltersToCreate: ViewFilter[]) => {
if (!viewFiltersToCreate.length) return;
if (!availableFilters) {
return;
}
// return createViewFiltersMutation({
// variables: {
// data: filters.map((filter) => ({
// displayValue: filter.displayValue ?? filter.value,
// key: filter.key,
// name:
// availableFilters.find(({ key }) => key === filter.key)
// ?.label ?? '',
// operand: filter.operand,
// value: filter.value,
// viewId: viewId ?? currentViewId,
// })),
// },
// });
return Promise.all(
viewFiltersToCreate.map((viewFilter) =>
apolloClient.mutate({
mutation: createOneMutation,
variables: {
input: {
fieldId: viewFilter.fieldId,
viewId: viewId ?? currentViewId,
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
},
},
refetchQueries: [findManyQuery],
}),
),
);
};
const _updateViewFilters = (filters: Filter[]) => {
if (!currentViewId || !filters.length) return;
const updateViewFilters = (viewFiltersToUpdate: ViewFilter[]) => {
if (!viewFiltersToUpdate.length) return;
// return Promise.all(
// filters.map((filter) =>
// updateViewFilterMutation({
// variables: {
// data: {
// displayValue: filter.displayValue ?? filter.value,
// operand: filter.operand,
// value: filter.value,
// },
// where: {
// viewId_key: {
// key: filter.key,
// viewId: viewId ?? currentViewId,
// },
// },
// },
// }),
// ),
// );
return Promise.all(
viewFiltersToUpdate.map((viewFilter) =>
apolloClient.mutate({
mutation: updateOneMutation,
variables: {
idToUpdate: viewFilter.id,
input: {
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
},
},
}),
),
);
};
const _deleteViewFilters = (filterKeys: string[]) => {
if (!currentViewId || !filterKeys.length) return;
const deleteViewFilters = (viewFilterIdsToDelete: string[]) => {
if (!viewFilterIdsToDelete.length) return;
// return deleteViewFiltersMutation({
// variables: {
// where: {
// key: { in: filterKeys },
// viewId: { equals: viewId ?? currentViewId },
// },
// },
// });
// Todo
};
const currentViewFilters = snapshot
@ -97,7 +92,7 @@ export const useViewFilters = (viewScopeId: string) => {
.getLoadable(
savedViewFiltersByKeyScopedFamilySelector({
scopeId: viewScopeId,
viewId: currentViewId,
viewId: viewId ?? currentViewId,
}),
)
.getValue();
@ -109,32 +104,24 @@ export const useViewFilters = (viewScopeId: string) => {
return;
}
const availableFilters = snapshot
.getLoadable(
availableFiltersScopedState({
scopeId: viewScopeId,
}),
)
.getValue();
const filtersToCreate = currentViewFilters.filter(
(filter) => !savedViewFiltersByKey[filter.key],
(filter) => !savedViewFiltersByKey[filter.fieldId],
);
await _createViewFilters(filtersToCreate, availableFilters);
await createViewFilters(filtersToCreate);
const filtersToUpdate = currentViewFilters.filter(
(filter) =>
savedViewFiltersByKey[filter.key] &&
(savedViewFiltersByKey[filter.key].operand !== filter.operand ||
savedViewFiltersByKey[filter.key].value !== filter.value),
savedViewFiltersByKey[filter.fieldId] &&
(savedViewFiltersByKey[filter.fieldId].operand !== filter.operand ||
savedViewFiltersByKey[filter.fieldId].value !== filter.value),
);
await _updateViewFilters(filtersToUpdate);
await updateViewFilters(filtersToUpdate);
const filterKeys = currentViewFilters.map((filter) => filter.key);
const filterKeys = currentViewFilters.map((filter) => filter.fieldId);
const filterKeysToDelete = Object.keys(savedViewFiltersByKey).filter(
(previousFilterKey) => !filterKeys.includes(previousFilterKey),
);
await _deleteViewFilters(filterKeysToDelete);
await deleteViewFilters(filterKeysToDelete);
set(
savedViewFiltersScopedFamilyState({
scopeId: viewScopeId,
@ -143,8 +130,102 @@ export const useViewFilters = (viewScopeId: string) => {
currentViewFilters,
);
},
[viewScopeId],
[
apolloClient,
createOneMutation,
findManyQuery,
updateOneMutation,
viewScopeId,
],
);
return { persistViewFilters };
const upsertViewFilter = useRecoilCallback(
({ snapshot }) =>
(filterToUpsert: Filter) => {
const currentViewId = snapshot
.getLoadable(currentViewIdScopedState({ scopeId: viewScopeId }))
.getValue();
if (!currentViewId) {
return;
}
const savedViewFiltersByKey = snapshot
.getLoadable(
savedViewFiltersByKeyScopedFamilySelector({
scopeId: viewScopeId,
viewId: currentViewId,
}),
)
.getValue();
if (!savedViewFiltersByKey) {
return;
}
const onViewFiltersChange = snapshot
.getLoadable(onViewFiltersChangeScopedState({ scopeId: viewScopeId }))
.getValue();
const existingSavedFilterId =
savedViewFiltersByKey[filterToUpsert.fieldId]?.id;
setCurrentViewFilters?.((filters) => {
const newViewFilters = produce(filters, (filtersDraft) => {
const existingFilterIndex = filtersDraft.findIndex(
(filter) => filter.fieldId === filterToUpsert.fieldId,
);
if (existingFilterIndex === -1) {
filtersDraft.push({
...filterToUpsert,
id: existingSavedFilterId,
});
return filtersDraft;
}
filtersDraft[existingFilterIndex] = {
...filterToUpsert,
id: existingSavedFilterId,
};
});
onViewFiltersChange?.(newViewFilters);
return newViewFilters;
});
},
);
const removeViewFilter = useRecoilCallback(
({ snapshot }) =>
(fieldId: string) => {
const currentViewId = snapshot
.getLoadable(currentViewIdScopedState({ scopeId: viewScopeId }))
.getValue();
if (!currentViewId) {
return;
}
const onViewFiltersChange = snapshot
.getLoadable(onViewFiltersChangeScopedState({ scopeId: viewScopeId }))
.getValue();
const currentViewFilters = snapshot
.getLoadable(
currentViewFiltersScopedFamilyState({
scopeId: viewScopeId,
familyKey: currentViewId,
}),
)
.getValue();
const newViewFilters = currentViewFilters.filter((filter) => {
return filter.fieldId !== fieldId;
});
setCurrentViewFilters?.(newViewFilters);
onViewFiltersChange?.(newViewFilters);
},
);
return { persistViewFilters, removeViewFilter, upsertViewFilter };
};

View File

@ -1,16 +1,25 @@
import { useApolloClient } from '@apollo/client';
import { produce } from 'immer';
import { useRecoilCallback } from 'recoil';
import { useFindOneMetadataObject } from '@/metadata/hooks/useFindOneMetadataObject';
import { Sort } from '@/ui/data/sort/types/Sort';
import { currentViewIdScopedState } from '@/views/states/currentViewIdScopedState';
import { currentViewSortsScopedFamilyState } from '@/views/states/currentViewSortsScopedFamilyState';
import { onViewSortsChangeScopedState } from '@/views/states/onViewSortsChangeScopedState';
import { savedViewSortsScopedFamilyState } from '@/views/states/savedViewSortsScopedFamilyState';
import { savedViewSortsByKeyScopedFamilySelector } from '@/views/states/selectors/savedViewSortsByKeyScopedFamilySelector';
import { ViewSort } from '@/views/types/ViewSort';
import { useViewStates } from '../useViewStates';
import { useViewSetStates } from '../useViewSetStates';
export const useViewSorts = (viewScopeId: string) => {
const { setCurrentViewSorts } = useViewStates(viewScopeId);
const { updateOneMutation, createOneMutation, findManyQuery } =
useFindOneMetadataObject({
objectNameSingular: 'viewSortV2',
});
const apolloClient = useApolloClient();
const { setCurrentViewSorts } = useViewSetStates(viewScopeId);
const persistViewSorts = useRecoilCallback(
({ snapshot, set }) =>
@ -22,54 +31,48 @@ export const useViewSorts = (viewScopeId: string) => {
return;
}
const _createViewSorts = (sorts: Sort[]) => {
if (!currentViewId || !sorts.length) return;
const createViewSorts = (viewSortsToCreate: ViewSort[]) => {
if (!viewSortsToCreate.length) return;
// return createViewSortsMutation({
// variables: {
// data: sorts.map((sort) => ({
// key: sort.key,
// direction: sort.direction as ViewSortDirection,
// name: sort.definition.label,
// viewId: viewId ?? currentViewId,
// })),
// },
// });
return Promise.all(
viewSortsToCreate.map((viewSort) =>
apolloClient.mutate({
mutation: createOneMutation,
variables: {
input: {
fieldId: viewSort.fieldId,
viewId: viewId ?? currentViewId,
direction: viewSort.direction,
},
},
refetchQueries: [findManyQuery],
}),
),
);
};
const _updateViewSorts = (sorts: Sort[]) => {
if (!currentViewId || !sorts.length) return;
const updateViewSorts = (viewSortsToUpdate: ViewSort[]) => {
if (!viewSortsToUpdate.length) return;
// return Promise.all(
// sorts.map((sort) =>
// updateViewSortMutation({
// variables: {
// data: {
// direction: sort.direction as ViewSortDirection,
// },
// where: {
// viewId_key: {
// key: sort.key,
// viewId: viewId ?? currentViewId,
// },
// },
// },
// }),
// ),
// );
return Promise.all(
viewSortsToUpdate.map((viewSort) =>
apolloClient.mutate({
mutation: updateOneMutation,
variables: {
idToUpdate: viewSort.id,
input: {
direction: viewSort.direction,
},
},
}),
),
);
};
const _deleteViewSorts = (sortKeys: string[]) => {
if (!currentViewId || !sortKeys.length) return;
const deleteViewSorts = (viewSortIdsToDelete: string[]) => {
if (!viewSortIdsToDelete.length) return;
// return deleteViewSortsMutation({
// variables: {
// where: {
// key: { in: sortKeys },
// viewId: { equals: viewId ?? currentViewId },
// },
// },
// });
// Todo
};
const currentViewSorts = snapshot
@ -85,7 +88,7 @@ export const useViewSorts = (viewScopeId: string) => {
.getLoadable(
savedViewSortsByKeyScopedFamilySelector({
scopeId: viewScopeId,
viewId: currentViewId,
viewId: viewId ?? currentViewId,
}),
)
.getValue();
@ -98,22 +101,23 @@ export const useViewSorts = (viewScopeId: string) => {
}
const sortsToCreate = currentViewSorts.filter(
(sort) => !savedViewSortsByKey[sort.key],
(sort) => !savedViewSortsByKey[sort.fieldId],
);
await _createViewSorts(sortsToCreate);
await createViewSorts(sortsToCreate);
const sortsToUpdate = currentViewSorts.filter(
(sort) =>
savedViewSortsByKey[sort.key] &&
savedViewSortsByKey[sort.key].direction !== sort.direction,
savedViewSortsByKey[sort.fieldId] &&
savedViewSortsByKey[sort.fieldId].direction !== sort.direction,
);
await _updateViewSorts(sortsToUpdate);
await updateViewSorts(sortsToUpdate);
const sortKeys = currentViewSorts.map((sort) => sort.key);
const sortKeys = currentViewSorts.map((sort) => sort.fieldId);
const sortKeysToDelete = Object.keys(savedViewSortsByKey).filter(
(previousSortKey) => !sortKeys.includes(previousSortKey),
);
await _deleteViewSorts(sortKeysToDelete);
await deleteViewSorts(sortKeysToDelete);
set(
savedViewSortsScopedFamilyState({
scopeId: viewScopeId,
@ -122,24 +126,99 @@ export const useViewSorts = (viewScopeId: string) => {
currentViewSorts,
);
},
[viewScopeId],
[
apolloClient,
createOneMutation,
findManyQuery,
updateOneMutation,
viewScopeId,
],
);
const upsertViewSort = (sortToUpsert: Sort) => {
setCurrentViewSorts?.((sorts) => {
return produce(sorts, (sortsDraft) => {
const index = sortsDraft.findIndex(
(sort) => sort.key === sortToUpsert.key,
);
const upsertViewSort = useRecoilCallback(
({ snapshot }) =>
(sortToUpsert: Sort) => {
const currentViewId = snapshot
.getLoadable(currentViewIdScopedState({ scopeId: viewScopeId }))
.getValue();
if (index === -1) {
sortsDraft.push(sortToUpsert);
} else {
sortsDraft[index] = sortToUpsert;
if (!currentViewId) {
return;
}
});
});
};
return { persistViewSorts, upsertViewSort };
const savedViewSortsByKey = snapshot
.getLoadable(
savedViewSortsByKeyScopedFamilySelector({
scopeId: viewScopeId,
viewId: currentViewId,
}),
)
.getValue();
if (!savedViewSortsByKey) {
return;
}
const onViewSortsChange = snapshot
.getLoadable(onViewSortsChangeScopedState({ scopeId: viewScopeId }))
.getValue();
const existingSavedSortId =
savedViewSortsByKey[sortToUpsert.fieldId]?.id;
setCurrentViewSorts?.((sorts) => {
const newViewSorts = produce(sorts, (sortsDraft) => {
const existingSortIndex = sortsDraft.findIndex(
(sort) => sort.fieldId === sortToUpsert.fieldId,
);
if (existingSortIndex === -1) {
sortsDraft.push({ ...sortToUpsert, id: existingSavedSortId });
return sortsDraft;
}
sortsDraft[existingSortIndex] = {
...sortToUpsert,
id: existingSavedSortId,
};
});
onViewSortsChange?.(newViewSorts);
return newViewSorts;
});
},
);
const removeViewSort = useRecoilCallback(
({ snapshot }) =>
(fieldId: string) => {
const currentViewId = snapshot
.getLoadable(currentViewIdScopedState({ scopeId: viewScopeId }))
.getValue();
if (!currentViewId) {
return;
}
const onViewSortsChange = snapshot
.getLoadable(onViewSortsChangeScopedState({ scopeId: viewScopeId }))
.getValue();
const currentViewSorts = snapshot
.getLoadable(
currentViewSortsScopedFamilyState({
scopeId: viewScopeId,
familyKey: currentViewId,
}),
)
.getValue();
const newViewSorts = currentViewSorts.filter((filter) => {
return filter.fieldId !== fieldId;
});
setCurrentViewSorts?.(newViewSorts);
onViewSortsChange?.(newViewSorts);
},
);
return { persistViewSorts, upsertViewSort, removeViewSort };
};

View File

@ -1,13 +1,21 @@
import { useApolloClient } from '@apollo/client';
import { useRecoilCallback } from 'recoil';
import { useFindOneMetadataObject } from '@/metadata/hooks/useFindOneMetadataObject';
import { viewObjectIdScopeState } from '@/views/states/viewObjectIdScopeState';
import { viewTypeScopedState } from '@/views/states/viewTypeScopedState';
import { View } from '@/views/types/View';
export const useViews = (scopeId: string) => {
const { updateOneMutation, createOneMutation, findManyQuery } =
useFindOneMetadataObject({
objectNameSingular: 'viewV2',
});
const apolloClient = useApolloClient();
const createView = useRecoilCallback(
({ snapshot }) =>
async (_view: Pick<View, 'id' | 'name'>) => {
async (view: Pick<View, 'id' | 'name'>) => {
const viewObjectId = await snapshot
.getLoadable(viewObjectIdScopeState({ scopeId }))
.getValue();
@ -19,27 +27,31 @@ export const useViews = (scopeId: string) => {
if (!viewObjectId || !viewType) {
return;
}
// await createViewMutation({
// variables: {
// data: {
// ...view,
// objectId: viewObjectId,
// type: viewType,
// },
// },
// refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
// });
await apolloClient.mutate({
mutation: createOneMutation,
variables: {
input: {
...view,
objectId: viewObjectId,
type: viewType,
},
},
refetchQueries: [findManyQuery],
});
},
);
const updateView = async (_view: View) => {
// await updateViewMutation({
// variables: {
// data: { name: view.name },
// where: { id: view.id },
// },
// refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
// });
const updateView = async (view: View) => {
await apolloClient.mutate({
mutation: updateOneMutation,
variables: {
idToUpdate: view.id,
input: {
...view,
},
},
refetchQueries: [findManyQuery],
});
};
const deleteView = async (_viewId: string) => {

View File

@ -1,15 +0,0 @@
import { useView } from '@/views/hooks/useView';
export const useRemoveFilter = () => {
const { setCurrentViewFilters } = useView();
const removeFilter = (filterKey: string) => {
setCurrentViewFilters?.((filters) => {
return filters.filter((filter) => {
return filter.key !== filterKey;
});
});
};
return removeFilter;
};

View File

@ -1,26 +0,0 @@
import { produce } from 'immer';
import { Filter } from '@/ui/data/filter/types/Filter';
import { useView } from '@/views/hooks/useView';
export const useUpsertFilter = () => {
const { setCurrentViewFilters } = useView();
const upsertFilter = (filterToUpsert: Filter) => {
setCurrentViewFilters?.((filters) => {
return produce(filters, (filtersDraft) => {
const index = filtersDraft.findIndex(
(filter) => filter.key === filterToUpsert.key,
);
if (index === -1) {
filtersDraft.push(filterToUpsert);
} else {
filtersDraft[index] = filterToUpsert;
}
});
});
};
return upsertFilter;
};

View File

@ -10,8 +10,12 @@ import { currentViewFieldsScopedFamilyState } from '../states/currentViewFieldsS
import { currentViewFiltersScopedFamilyState } from '../states/currentViewFiltersScopedFamilyState';
import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
import { currentViewSortsScopedFamilyState } from '../states/currentViewSortsScopedFamilyState';
import { onViewFieldsChangeScopedState } from '../states/onViewFieldsChangeScopedState';
import { onViewFiltersChangeScopedState } from '../states/onViewFiltersChangeScopedState';
import { onViewSortsChangeScopedState } from '../states/onViewSortsChangeScopedState';
import { savedViewFiltersScopedFamilyState } from '../states/savedViewFiltersScopedFamilyState';
import { savedViewSortsScopedFamilyState } from '../states/savedViewSortsScopedFamilyState';
import { currentViewScopedSelector } from '../states/selectors/currentViewScopedSelector';
import { viewEditModeScopedState } from '../states/viewEditModeScopedState';
import { viewsScopedState } from '../states/viewsScopedState';
@ -19,7 +23,7 @@ import { useViewFields } from './internal/useViewFields';
import { useViewFilters } from './internal/useViewFilters';
import { useViews } from './internal/useViews';
import { useViewSorts } from './internal/useViewSorts';
import { useViewStates } from './useViewStates';
import { useViewSetStates } from './useViewSetStates';
type UseViewProps = {
viewScopeId?: string;
@ -42,33 +46,81 @@ export const useView = (props?: UseViewProps) => {
setEntityCountInCurrentView,
setIsViewBarExpanded,
setAvailableSorts,
setAvailableSortDefinitions,
setCurrentViewSorts,
setSavedViewSorts,
setAvailableFilters,
setAvailableFilterDefinitions,
setCurrentViewFilters,
setSavedViewFilters,
setAvailableFields,
setAvailableFieldDefinitions,
setCurrentViewFields,
setSavedViewFields,
} = useViewStates(scopeId);
const { persistViewSorts, upsertViewSort } = useViewSorts(scopeId);
const { persistViewFilters } = useViewFilters(scopeId);
setOnViewFieldsChange,
setOnViewFiltersChange,
setOnViewSortsChange,
} = useViewSetStates(scopeId);
const { persistViewSorts, upsertViewSort, removeViewSort } =
useViewSorts(scopeId);
const { persistViewFilters, upsertViewFilter, removeViewFilter } =
useViewFilters(scopeId);
const { persistViewFields } = useViewFields(scopeId);
const { createView: internalCreateView, deleteView: internalDeleteView } =
useViews(scopeId);
const {
createView: internalCreateView,
updateView: internalUpdateView,
deleteView: internalDeleteView,
} = useViews(scopeId);
const [_, setSearchParams] = useSearchParams();
const changeView = useCallback(
const changeViewInUrl = useCallback(
(viewId: string) => {
setSearchParams({ view: viewId });
},
[setSearchParams],
);
const loadView = useRecoilCallback(({ snapshot }) => (viewId: string) => {
setCurrentViewId?.(viewId);
const currentViewFields = snapshot
.getLoadable(
currentViewFieldsScopedFamilyState({ scopeId, familyKey: viewId }),
)
.getValue();
const onViewFieldsChange = snapshot
.getLoadable(onViewFieldsChangeScopedState({ scopeId }))
.getValue();
onViewFieldsChange?.(currentViewFields);
const currentViewFilters = snapshot
.getLoadable(
currentViewFiltersScopedFamilyState({ scopeId, familyKey: viewId }),
)
.getValue();
const onViewFiltersChange = snapshot
.getLoadable(onViewFiltersChangeScopedState({ scopeId }))
.getValue();
onViewFiltersChange?.(currentViewFilters);
const currentViewSorts = snapshot
.getLoadable(
currentViewSortsScopedFamilyState({ scopeId, familyKey: viewId }),
)
.getValue();
const onViewSortsChange = snapshot
.getLoadable(onViewSortsChangeScopedState({ scopeId }))
.getValue();
onViewSortsChange?.(currentViewSorts);
});
const resetViewBar = useRecoilCallback(({ snapshot }) => () => {
const savedViewFilters = snapshot
.getLoadable(
@ -95,7 +147,6 @@ export const useView = (props?: UseViewProps) => {
setCurrentViewSorts?.(savedViewSorts);
}
setViewEditMode?.('none');
setIsViewBarExpanded?.(false);
});
const createView = useRecoilCallback(
@ -156,10 +207,10 @@ export const useView = (props?: UseViewProps) => {
await persistViewFilters(newViewId);
await persistViewSorts(newViewId);
changeView(newViewId);
changeViewInUrl(newViewId);
},
[
changeView,
changeViewInUrl,
currentViewId,
internalCreateView,
persistViewFields,
@ -195,15 +246,32 @@ export const useView = (props?: UseViewProps) => {
const handleViewNameSubmit = useRecoilCallback(
({ snapshot }) =>
async (name?: string) => {
if (!name) {
return;
}
const viewEditMode = snapshot
.getLoadable(viewEditModeScopedState({ scopeId }))
.getValue();
const currentView = snapshot
.getLoadable(currentViewScopedSelector(scopeId))
.getValue();
if (!currentView) {
return;
}
if (viewEditMode === 'create' && name) {
await createView(name);
} else {
await internalUpdateView({
...currentView,
name,
});
}
},
[createView, scopeId],
[createView, internalUpdateView, scopeId],
);
return {
@ -224,20 +292,28 @@ export const useView = (props?: UseViewProps) => {
setViewType,
setEntityCountInCurrentView,
setAvailableSorts,
setAvailableSortDefinitions,
setCurrentViewSorts,
setSavedViewSorts,
upsertViewSort,
removeViewSort,
setAvailableFilters,
setAvailableFilterDefinitions,
setCurrentViewFilters,
setSavedViewFilters,
upsertViewFilter,
removeViewFilter,
setAvailableFields,
setAvailableFieldDefinitions,
setCurrentViewFields,
setSavedViewFields,
persistViewFields,
changeView,
changeViewInUrl,
loadView,
setOnViewFieldsChange,
setOnViewFiltersChange,
setOnViewSortsChange,
};
};

View File

@ -5,9 +5,9 @@ import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRec
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { ViewScopeInternalContext } from '../scopes/scope-internal-context/ViewScopeInternalContext';
import { availableFieldsScopedState } from '../states/availableFieldsScopedState';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { availableSortsScopedState } from '../states/availableSortsScopedState';
import { availableFieldDefinitionsScopedState } from '../states/availableFieldDefinitionsScopedState';
import { availableFilterDefinitionsScopedState } from '../states/availableFilterDefinitionsScopedState';
import { availableSortDefinitionsScopedState } from '../states/availableSortDefinitionsScopedState';
import { currentViewFieldsScopedFamilyState } from '../states/currentViewFieldsScopedFamilyState';
import { currentViewFiltersScopedFamilyState } from '../states/currentViewFiltersScopedFamilyState';
import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
@ -31,17 +31,14 @@ import { viewObjectIdScopeState } from '../states/viewObjectIdScopeState';
import { viewsScopedState } from '../states/viewsScopedState';
import { viewTypeScopedState } from '../states/viewTypeScopedState';
export const useViewInternalStates = (
viewScopeId?: string,
viewId?: string,
) => {
export const useViewGetStates = (viewScopeId?: string, viewId?: string) => {
const scopeId = useAvailableScopeIdOrThrow(
ViewScopeInternalContext,
viewScopeId,
);
// View
const [currentViewId, setCurrentViewId] = useRecoilScopedStateV2(
const [currentViewId] = useRecoilScopedStateV2(
currentViewIdScopedState,
scopeId,
);
@ -50,39 +47,38 @@ export const useViewInternalStates = (
const currentView = useRecoilValue(currentViewScopedSelector(scopeId));
const [viewEditMode, setViewEditMode] = useRecoilScopedStateV2(
const [viewEditMode] = useRecoilScopedStateV2(
viewEditModeScopedState,
scopeId,
);
const [views, setViews] = useRecoilScopedStateV2(viewsScopedState, scopeId);
const [views] = useRecoilScopedStateV2(viewsScopedState, scopeId);
const [viewObjectId, setViewObjectId] = useRecoilScopedStateV2(
const [viewObjectId] = useRecoilScopedStateV2(
viewObjectIdScopeState,
scopeId,
);
const [viewType, setViewType] = useRecoilScopedStateV2(
viewTypeScopedState,
const [viewType] = useRecoilScopedStateV2(viewTypeScopedState, scopeId);
const [entityCountInCurrentView] = useRecoilScopedStateV2(
entityCountInCurrentViewScopedState,
scopeId,
);
const [entityCountInCurrentView, setEntityCountInCurrentView] =
useRecoilScopedStateV2(entityCountInCurrentViewScopedState, scopeId);
const [isViewBarExpanded, setIsViewBarExpanded] = useRecoilScopedStateV2(
const [isViewBarExpanded] = useRecoilScopedStateV2(
isViewBarExpandedScopedState,
scopeId,
);
// ViewSorts
const [currentViewSorts, setCurrentViewSorts] = useRecoilScopedFamilyState(
const [currentViewSorts] = useRecoilScopedFamilyState(
currentViewSortsScopedFamilyState,
scopeId,
familyItemId,
);
const [savedViewSorts, setSavedViewSorts] = useRecoilScopedFamilyState(
const [savedViewSorts] = useRecoilScopedFamilyState(
savedViewSortsScopedFamilyState,
scopeId,
familyItemId,
@ -95,8 +91,8 @@ export const useViewInternalStates = (
}),
);
const [availableSorts, setAvailableSorts] = useRecoilScopedStateV2(
availableSortsScopedState,
const [availableSortDefinitions] = useRecoilScopedStateV2(
availableSortDefinitionsScopedState,
scopeId,
);
@ -108,14 +104,13 @@ export const useViewInternalStates = (
);
// ViewFilters
const [currentViewFilters, setCurrentViewFilters] =
useRecoilScopedFamilyState(
currentViewFiltersScopedFamilyState,
scopeId,
familyItemId,
);
const [currentViewFilters] = useRecoilScopedFamilyState(
currentViewFiltersScopedFamilyState,
scopeId,
familyItemId,
);
const [savedViewFilters, setSavedViewFilters] = useRecoilScopedFamilyState(
const [savedViewFilters] = useRecoilScopedFamilyState(
savedViewFiltersScopedFamilyState,
scopeId,
familyItemId,
@ -128,8 +123,8 @@ export const useViewInternalStates = (
}),
);
const [availableFilters, setAvailableFilters] = useRecoilScopedStateV2(
availableFiltersScopedState,
const [availableFilterDefinitions] = useRecoilScopedStateV2(
availableFilterDefinitionsScopedState,
scopeId,
);
@ -141,18 +136,18 @@ export const useViewInternalStates = (
);
// ViewFields
const [availableFields, setAvailableFields] = useRecoilScopedStateV2(
availableFieldsScopedState,
const [availableFieldDefinitions] = useRecoilScopedStateV2(
availableFieldDefinitionsScopedState,
scopeId,
);
const [currentViewFields, setCurrentViewFields] = useRecoilScopedFamilyState(
const [currentViewFields] = useRecoilScopedFamilyState(
currentViewFieldsScopedFamilyState,
scopeId,
familyItemId,
);
const [savedViewFields, setSavedViewFields] = useRecoilScopedFamilyState(
const [savedViewFields] = useRecoilScopedFamilyState(
savedViewFieldsScopedFamilyState,
scopeId,
familyItemId,
@ -166,17 +161,17 @@ export const useViewInternalStates = (
);
// ViewChangeHandlers
const [onViewSortsChange, setOnViewSortsChange] = useRecoilScopedStateV2(
const [onViewSortsChange] = useRecoilScopedStateV2(
onViewSortsChangeScopedState,
scopeId,
);
const [onViewFiltersChange, setOnViewFiltersChange] = useRecoilScopedStateV2(
const [onViewFiltersChange] = useRecoilScopedStateV2(
onViewFiltersChangeScopedState,
scopeId,
);
const [onViewFieldsChange, setOnViewFieldsChange] = useRecoilScopedStateV2(
const [onViewFieldsChange] = useRecoilScopedStateV2(
onViewFieldsChangeScopedState,
scopeId,
);
@ -184,52 +179,33 @@ export const useViewInternalStates = (
return {
currentViewId,
currentView,
setCurrentViewId,
isViewBarExpanded,
setIsViewBarExpanded,
views,
setViews,
viewEditMode,
setViewEditMode,
viewObjectId,
setViewObjectId,
viewType,
setViewType,
entityCountInCurrentView,
setEntityCountInCurrentView,
availableSorts,
setAvailableSorts,
availableSortDefinitions,
currentViewSorts,
setCurrentViewSorts,
savedViewSorts,
savedViewSortsByKey,
setSavedViewSorts,
canPersistSorts,
availableFilters,
setAvailableFilters,
availableFilterDefinitions,
currentViewFilters,
setCurrentViewFilters,
savedViewFilters,
savedViewFiltersByKey,
setSavedViewFilters,
canPersistFilters,
availableFields,
setAvailableFields,
availableFieldDefinitions,
currentViewFields,
savedViewFieldsByKey,
setCurrentViewFields,
savedViewFields,
setSavedViewFields,
onViewSortsChange,
setOnViewSortsChange,
onViewFiltersChange,
setOnViewFiltersChange,
onViewFieldsChange,
setOnViewFieldsChange,
};
};

View File

@ -4,15 +4,18 @@ import { useSetRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/use
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { ViewScopeInternalContext } from '../scopes/scope-internal-context/ViewScopeInternalContext';
import { availableFieldsScopedState } from '../states/availableFieldsScopedState';
import { availableFiltersScopedState } from '../states/availableFiltersScopedState';
import { availableSortsScopedState } from '../states/availableSortsScopedState';
import { availableFieldDefinitionsScopedState } from '../states/availableFieldDefinitionsScopedState';
import { availableFilterDefinitionsScopedState } from '../states/availableFilterDefinitionsScopedState';
import { availableSortDefinitionsScopedState } from '../states/availableSortDefinitionsScopedState';
import { currentViewFieldsScopedFamilyState } from '../states/currentViewFieldsScopedFamilyState';
import { currentViewFiltersScopedFamilyState } from '../states/currentViewFiltersScopedFamilyState';
import { currentViewIdScopedState } from '../states/currentViewIdScopedState';
import { currentViewSortsScopedFamilyState } from '../states/currentViewSortsScopedFamilyState';
import { entityCountInCurrentViewScopedState } from '../states/entityCountInCurrentViewScopedState';
import { isViewBarExpandedScopedState } from '../states/isViewBarExpandedScopedState';
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';
@ -21,7 +24,7 @@ import { viewObjectIdScopeState } from '../states/viewObjectIdScopeState';
import { viewsScopedState } from '../states/viewsScopedState';
import { viewTypeScopedState } from '../states/viewTypeScopedState';
export const useViewStates = (viewScopeId?: string, viewId?: string) => {
export const useViewSetStates = (viewScopeId?: string, viewId?: string) => {
const scopeId = useAvailableScopeIdOrThrow(
ViewScopeInternalContext,
viewScopeId,
@ -71,8 +74,8 @@ export const useViewStates = (viewScopeId?: string, viewId?: string) => {
familyItemId,
);
const setAvailableSorts = useSetRecoilScopedStateV2(
availableSortsScopedState,
const setAvailableSortDefinitions = useSetRecoilScopedStateV2(
availableSortDefinitionsScopedState,
scopeId,
);
@ -89,14 +92,14 @@ export const useViewStates = (viewScopeId?: string, viewId?: string) => {
familyItemId,
);
const setAvailableFilters = useSetRecoilScopedStateV2(
availableFiltersScopedState,
const setAvailableFilterDefinitions = useSetRecoilScopedStateV2(
availableFilterDefinitionsScopedState,
scopeId,
);
// ViewFields
const setAvailableFields = useSetRecoilScopedStateV2(
availableFieldsScopedState,
const setAvailableFieldDefinitions = useSetRecoilScopedStateV2(
availableFieldDefinitionsScopedState,
scopeId,
);
@ -112,6 +115,21 @@ export const useViewStates = (viewScopeId?: string, viewId?: string) => {
familyItemId,
);
const setOnViewFieldsChange = useSetRecoilScopedStateV2(
onViewFieldsChangeScopedState,
scopeId,
);
const setOnViewFiltersChange = useSetRecoilScopedStateV2(
onViewFiltersChangeScopedState,
scopeId,
);
const setOnViewSortsChange = useSetRecoilScopedStateV2(
onViewSortsChangeScopedState,
scopeId,
);
return {
currentViewId,
setCurrentViewId,
@ -123,16 +141,20 @@ export const useViewStates = (viewScopeId?: string, viewId?: string) => {
setViewEditMode,
setEntityCountInCurrentView,
setAvailableSorts,
setAvailableSortDefinitions,
setCurrentViewSorts,
setSavedViewSorts,
setAvailableFilters,
setAvailableFilterDefinitions,
setCurrentViewFilters,
setSavedViewFilters,
setAvailableFields,
setAvailableFieldDefinitions,
setCurrentViewFields,
setSavedViewFields,
setOnViewFieldsChange,
setOnViewFiltersChange,
setOnViewSortsChange,
};
};

View File

@ -3,7 +3,6 @@ import { useEffect } from 'react';
import { Filter } from '@/ui/data/filter/types/Filter';
import { Sort } from '@/ui/data/sort/types/Sort';
import { useView } from '@/views/hooks/useView';
import { useViewInternalStates } from '@/views/hooks/useViewInternalStates';
import { ViewField } from '@/views/types/ViewField';
type ViewScopeInitEffectProps = {
@ -14,17 +13,15 @@ type ViewScopeInitEffectProps = {
};
export const ViewScopeInitEffect = ({
viewScopeId,
onViewSortsChange,
onViewFiltersChange,
onViewFieldsChange,
}: ViewScopeInitEffectProps) => {
const { currentViewId } = useView();
const {
setOnViewSortsChange,
setOnViewFieldsChange,
setOnViewFiltersChange,
} = useViewInternalStates(viewScopeId, currentViewId);
} = useView();
useEffect(() => {
setOnViewSortsChange(() => onViewSortsChange);

View File

@ -2,9 +2,9 @@ import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const availableFieldsScopedState = createScopedState<
export const availableFieldDefinitionsScopedState = createScopedState<
ColumnDefinition<FieldMetadata>[]
>({
key: 'availableFieldsScopedState',
key: 'availableFieldDefinitionsScopedState',
defaultValue: [],
});

View File

@ -1,9 +1,9 @@
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const availableFiltersScopedState = createScopedState<
export const availableFilterDefinitionsScopedState = createScopedState<
FilterDefinition[]
>({
key: 'availableFiltersScopedState',
key: 'availableFilterDefinitionsScopedState',
defaultValue: [],
});

View File

@ -1,7 +1,9 @@
import { SortDefinition } from '@/ui/data/sort/types/SortDefinition';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const availableSortsScopedState = createScopedState<SortDefinition[]>({
key: 'availableSortsScopedState',
export const availableSortDefinitionsScopedState = createScopedState<
SortDefinition[]
>({
key: 'availableSortDefinitionsScopedState',
defaultValue: [],
});

View File

@ -1,8 +1,9 @@
import { Filter } from '@/ui/data/filter/types/Filter';
import { createScopedFamilyState } from '@/ui/utilities/recoil-scope/utils/createScopedFamilyState';
import { ViewFilter } from '../types/ViewFilter';
export const currentViewFiltersScopedFamilyState = createScopedFamilyState<
Filter[],
ViewFilter[],
string
>({
key: 'currentViewFiltersScopedFamilyState',

View File

@ -1,8 +1,9 @@
import { Sort } from '@/ui/data/sort/types/Sort';
import { createScopedFamilyState } from '@/ui/utilities/recoil-scope/utils/createScopedFamilyState';
import { ViewSort } from '../types/ViewSort';
export const currentViewSortsScopedFamilyState = createScopedFamilyState<
Sort[],
ViewSort[],
string
>({
key: 'currentViewSortsScopedFamilyState',

View File

@ -1,8 +1,9 @@
import { Filter } from '@/ui/data/filter/types/Filter';
import { createScopedFamilyState } from '@/ui/utilities/recoil-scope/utils/createScopedFamilyState';
import { ViewFilter } from '../types/ViewFilter';
export const savedViewFiltersScopedFamilyState = createScopedFamilyState<
Filter[],
ViewFilter[],
string
>({
key: 'savedViewFiltersScopedFamilyState',

View File

@ -1,8 +1,9 @@
import { Sort } from '@/ui/data/sort/types/Sort';
import { createScopedFamilyState } from '@/ui/utilities/recoil-scope/utils/createScopedFamilyState';
import { ViewSort } from '../types/ViewSort';
export const savedViewSortsScopedFamilyState = createScopedFamilyState<
Sort[],
ViewSort[],
string
>({
key: 'savedViewSortsScopedFamilyState',

View File

@ -1,6 +1,6 @@
import { selectorFamily } from 'recoil';
import { Filter } from '@/ui/data/filter/types/Filter';
import { ViewFilter } from '@/views/types/ViewFilter';
import { savedViewFiltersScopedFamilyState } from '../savedViewFiltersScopedFamilyState';
@ -17,8 +17,8 @@ export const savedViewFiltersByKeyScopedFamilySelector = selectorFamily({
scopeId: scopeId,
familyKey: viewId,
}),
).reduce<Record<string, Filter>>(
(result, filter) => ({ ...result, [filter.key]: filter }),
).reduce<Record<string, ViewFilter>>(
(result, filter) => ({ ...result, [filter.fieldId]: filter }),
{},
);
},

View File

@ -1,6 +1,6 @@
import { selectorFamily } from 'recoil';
import { Sort } from '@/ui/data/sort/types/Sort';
import { ViewSort } from '@/views/types/ViewSort';
import { savedViewSortsScopedFamilyState } from '../savedViewSortsScopedFamilyState';
@ -17,8 +17,8 @@ export const savedViewSortsByKeyScopedFamilySelector = selectorFamily({
scopeId: scopeId,
familyKey: viewId,
}),
).reduce<Record<string, Sort>>(
(result, sort) => ({ ...result, [sort.key]: sort }),
).reduce<Record<string, ViewSort>>(
(result, sort) => ({ ...result, [sort.fieldId]: sort }),
{},
);
},

View File

@ -1,7 +1,11 @@
import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
export type ViewField = {
id: string;
fieldId: string;
position: number;
isVisible: boolean;
size: number;
definition: ColumnDefinition<FieldMetadata>;
};

View File

@ -0,0 +1,12 @@
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { ViewFilterOperand } from './ViewFilterOperand';
export type ViewFilter = {
id?: string;
fieldId: string;
operand: ViewFilterOperand;
value: string;
displayValue: string;
definition: FilterDefinition;
};

View File

@ -0,0 +1,9 @@
import { SortDefinition } from '@/ui/data/sort/types/SortDefinition';
import { SortDirection } from '@/ui/data/sort/types/SortDirection';
export type ViewSort = {
id?: string;
fieldId: string;
direction: SortDirection;
definition: SortDefinition;
};

View File

@ -12,5 +12,6 @@ export const columnDefinitionsToViewFields = (
position: columnDefinition.position,
size: columnDefinition.size,
isVisible: columnDefinition.isVisible ?? true,
definition: columnDefinition,
}));
};

View File

@ -0,0 +1,35 @@
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
import { BoardFieldDefinition } from '@/ui/layout/board/types/BoardFieldDefinition';
import { assertNotNull } from '~/utils/assert';
import { ViewField } from '../types/ViewField';
export const viewFieldsToBoardFieldDefinitions = (
viewFields: ViewField[],
fieldsMetadata: BoardFieldDefinition<FieldMetadata>[],
): BoardFieldDefinition<FieldMetadata>[] => {
return viewFields
.map((viewField) => {
const correspondingFieldMetadata = fieldsMetadata.find(
({ fieldId }) => viewField.fieldId === fieldId,
);
return correspondingFieldMetadata
? {
fieldId: viewField.fieldId,
label: correspondingFieldMetadata.label,
metadata: correspondingFieldMetadata.metadata,
entityChipDisplayMapper:
correspondingFieldMetadata.entityChipDisplayMapper,
infoTooltipContent: correspondingFieldMetadata.infoTooltipContent,
basePathToShowPage: correspondingFieldMetadata.basePathToShowPage,
Icon: correspondingFieldMetadata.Icon,
type: correspondingFieldMetadata.type,
position: viewField.position,
isVisible: viewField.isVisible,
viewFieldId: viewField.id,
}
: null;
})
.filter(assertNotNull);
};

View File

@ -0,0 +1,15 @@
import { Filter } from '@/ui/data/filter/types/Filter';
import { ViewFilter } from '../types/ViewFilter';
export const viewFiltersToFilters = (viewFilters: ViewFilter[]): Filter[] => {
return viewFilters.map((viewFilter) => {
return {
fieldId: viewFilter.fieldId,
value: viewFilter.value,
displayValue: viewFilter.displayValue,
operand: viewFilter.operand,
definition: viewFilter.definition,
};
});
};

View File

@ -0,0 +1,13 @@
import { Sort } from '@/ui/data/sort/types/Sort';
import { ViewSort } from '../types/ViewSort';
export const viewSortsToSorts = (viewSorts: ViewSort[]): Sort[] => {
return viewSorts.map((viewSort) => {
return {
fieldId: viewSort.fieldId,
direction: viewSort.direction,
definition: viewSort.definition,
};
});
};

View File

@ -28,7 +28,7 @@ import { getLogoUrlFromDomainName } from '~/utils';
import { CompanyNameEditableField } from '../../modules/companies/editable-field/components/CompanyNameEditableField';
import { ShowPageContainer } from '../../modules/ui/layout/page/ShowPageContainer';
import { companyShowFieldDefinition } from './constants/companyShowFieldDefinition';
import { companyShowFieldDefinitions } from './constants/companyShowFieldDefinitions';
export const CompanyShow = () => {
const companyId = useParams().companyId ?? '';
@ -90,7 +90,7 @@ export const CompanyShow = () => {
avatarType="squared"
/>
<PropertyBox extraPadding={true}>
{companyShowFieldDefinition.map((fieldDefinition) => {
{companyShowFieldDefinitions.map((fieldDefinition) => {
return (
<FieldContext.Provider
key={company.id + fieldDefinition.fieldId}

View File

@ -1,51 +0,0 @@
import { FilterDefinitionByEntity } from '@/ui/data/filter/types/FilterDefinitionByEntity';
import {
IconBuildingSkyscraper,
IconCalendarEvent,
IconLink,
IconMap,
IconUser,
IconUsers,
} from '@/ui/display/icon/index';
import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect';
import { Company } from '~/generated/graphql';
export const companyAvailableFilters: FilterDefinitionByEntity<Company>[] = [
{
key: 'name',
label: 'Name',
Icon: IconBuildingSkyscraper,
type: 'text',
},
{
key: 'employees',
label: 'Employees',
Icon: IconUsers,
type: 'number',
},
{
key: 'domainName',
label: 'URL',
Icon: IconLink,
type: 'text',
},
{
key: 'address',
label: 'Address',
Icon: IconMap,
type: 'text',
},
{
key: 'createdAt',
label: 'Created at',
Icon: IconCalendarEvent,
type: 'date',
},
{
key: 'accountOwnerId',
label: 'Account owner',
Icon: IconUser,
type: 'entity',
entitySelectComponent: <FilterDropdownUserSearchSelect />,
},
];

View File

@ -20,7 +20,7 @@ import {
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { User } from '~/generated/graphql';
export const companyShowFieldDefinition: FieldDefinition<FieldMetadata>[] = [
export const companyShowFieldDefinitions: FieldDefinition<FieldMetadata>[] = [
{
fieldId: 'domainName',
label: 'Domain name',

View File

@ -0,0 +1,52 @@
import { FilterDefinitionByEntity } from '@/ui/data/filter/types/FilterDefinitionByEntity';
import {
IconBuildingSkyscraper,
IconCalendarEvent,
IconLink,
IconMap,
IconUser,
IconUsers,
} from '@/ui/display/icon/index';
import { FilterDropdownUserSearchSelect } from '@/users/components/FilterDropdownUserSearchSelect';
import { Company } from '~/generated/graphql';
export const companyTableFilterDefinitions: FilterDefinitionByEntity<Company>[] =
[
{
fieldId: 'name',
label: 'Name',
Icon: IconBuildingSkyscraper,
type: 'text',
},
{
fieldId: 'employees',
label: 'Employees',
Icon: IconUsers,
type: 'number',
},
{
fieldId: 'domainName',
label: 'URL',
Icon: IconLink,
type: 'text',
},
{
fieldId: 'address',
label: 'Address',
Icon: IconMap,
type: 'text',
},
{
fieldId: 'createdAt',
label: 'Created at',
Icon: IconCalendarEvent,
type: 'date',
},
{
fieldId: 'accountOwnerId',
label: 'Account owner',
Icon: IconUser,
type: 'entity',
entitySelectComponent: <FilterDropdownUserSearchSelect />,
},
];

View File

@ -7,29 +7,29 @@ import {
IconUsers,
} from '@/ui/display/icon/index';
export const companyAvailableSorts: SortDefinition[] = [
export const companyTableSortDefinitions: SortDefinition[] = [
{
key: 'name',
fieldId: 'name',
label: 'Name',
Icon: IconBuildingSkyscraper,
},
{
key: 'employees',
fieldId: 'employees',
label: 'Employees',
Icon: IconUsers,
},
{
key: 'domainName',
fieldId: 'domainName',
label: 'Url',
Icon: IconLink,
},
{
key: 'address',
fieldId: 'address',
label: 'Address',
Icon: IconMap,
},
{
key: 'createdAt',
fieldId: 'createdAt',
label: 'Creation',
Icon: IconCalendarEvent,
},

View File

@ -1,3 +1,5 @@
import styled from '@emotion/styled';
import { CompanyBoard } from '@/companies/board/components/CompanyBoard';
import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
import { PipelineAddButton } from '@/pipeline/components/PipelineAddButton';
@ -11,6 +13,11 @@ import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'
import { useUpdatePipelineStageMutation } from '~/generated/graphql';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
const StyledBoardContainer = styled.div`
display: flex;
width: 100%;
`;
export const Opportunities = () => {
const { handlePipelineStageAdd, handlePipelineStageDelete } =
usePipelineStages();
@ -51,11 +58,13 @@ export const Opportunities = () => {
scopeId="opportunities"
CustomRecoilScopeContext={CompanyBoardRecoilScopeContext}
>
<CompanyBoard
onColumnAdd={handlePipelineStageAdd}
onColumnDelete={handlePipelineStageDelete}
onEditColumnTitle={handleEditColumnTitle}
/>
<StyledBoardContainer>
<CompanyBoard
onColumnAdd={handlePipelineStageAdd}
onColumnDelete={handlePipelineStageDelete}
onEditColumnTitle={handleEditColumnTitle}
/>
</StyledBoardContainer>
</RecoilScope>
</BoardOptionsContext.Provider>
</PageBody>

View File

@ -8,31 +8,31 @@ import {
} from '@/ui/display/icon/index';
import { PipelineProgress } from '~/generated/graphql';
import { FilterDropdownPeopleSearchSelect } from '../../modules/people/components/FilterDropdownPeopleSearchSelect';
import { FilterDropdownPeopleSearchSelect } from '../../../modules/people/components/FilterDropdownPeopleSearchSelect';
export const opportunitiesFilters: FilterDefinitionByEntity<PipelineProgress>[] =
export const opportunityBoardFilterDefinitions: FilterDefinitionByEntity<PipelineProgress>[] =
[
{
key: 'amount',
fieldId: 'amount',
label: 'Amount',
Icon: IconCurrencyDollar,
type: 'number',
},
{
key: 'closeDate',
fieldId: 'closeDate',
label: 'Close date',
Icon: IconCalendarEvent,
type: 'date',
},
{
key: 'companyId',
fieldId: 'companyId',
label: 'Company',
Icon: IconBuildingSkyscraper,
type: 'entity',
entitySelectComponent: <FilterDropdownCompanySearchSelect />,
},
{
key: 'pointOfContactId',
fieldId: 'pointOfContactId',
label: 'Point of contact',
Icon: IconUser,
type: 'entity',

View File

@ -1,19 +1,19 @@
import { SortDefinition } from '@/ui/data/sort/types/SortDefinition';
import { IconCalendarEvent, IconCurrencyDollar } from '@/ui/display/icon/index';
export const opportunitiesSorts: SortDefinition[] = [
export const opportunityBoardSortDefinitions: SortDefinition[] = [
{
key: 'createdAt',
fieldId: 'createdAt',
label: 'Creation',
Icon: IconCalendarEvent,
},
{
key: 'amount',
fieldId: 'amount',
label: 'Amount',
Icon: IconCurrencyDollar,
},
{
key: 'closeDate',
fieldId: 'closeDate',
label: 'Expected close date',
Icon: IconCalendarEvent,
},

View File

@ -2,12 +2,12 @@ import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard';
import { NewCompanyProgressButton } from '@/companies/components/NewCompanyProgressButton';
import { BoardOptions } from '@/ui/layout/board/types/BoardOptions';
import { opportunitiesFilters } from './opportunities-filters';
import { opportunitiesSorts } from './opportunities-sorts';
import { opportunityBoardFilterDefinitions } from './constants/opportunityBoardFilterDefinitions';
import { opportunityBoardSortDefinitions } from './constants/opportunityBoardSortDefinitions';
export const opportunitiesBoardOptions: BoardOptions = {
newCardComponent: <NewCompanyProgressButton />,
CardComponent: CompanyBoardCard,
filters: opportunitiesFilters,
sorts: opportunitiesSorts,
filterDefinitions: opportunityBoardFilterDefinitions,
sortDefinitions: opportunityBoardSortDefinitions,
};

View File

@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { v4 } from 'uuid';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { PeopleTable } from '@/people/table/components/PeopleTable';
import { PersonTable } from '@/people/table/components/PersonTable';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
import { DataTableActionBar } from '@/ui/data/data-table/action-bar/components/DataTableActionBar';
import { DataTableContextMenu } from '@/ui/data/data-table/context-menu/components/DataTableContextMenu';
@ -62,7 +62,7 @@ export const People = () => {
CustomRecoilScopeContext={TableRecoilScopeContext}
>
<StyledTableContainer>
<PeopleTable />
<PersonTable />
</StyledTableContainer>
<DataTableActionBar />
<DataTableContextMenu />

View File

@ -31,7 +31,7 @@ import {
import { PeopleFullNameEditableField } from '../../modules/people/editable-field/components/PeopleFullNameEditableField';
import { ShowPageContainer } from '../../modules/ui/layout/page/ShowPageContainer';
import { personShowFieldDefinition } from './constants/personShowFieldDefinition';
import { personShowFieldDefinition } from './constants/personShowFieldDefinitions';
export const PersonShow = () => {
const personId = useParams().personId ?? '';

Some files were not shown because too many files have changed in this diff Show More