Refactor ObjectDataTable to work with new views system (#2274)

Complete work
This commit is contained in:
Charles Bochet
2023-10-29 23:50:59 +01:00
committed by GitHub
parent 9bab28912d
commit d38497c46a
39 changed files with 578 additions and 373 deletions

View File

@ -1,4 +1,4 @@
import { useRecoilValue } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { selectedRowIdsSelector } from '@/ui/data/data-table/states/selectors/selectedRowIdsSelector'; import { selectedRowIdsSelector } from '@/ui/data/data-table/states/selectors/selectedRowIdsSelector';
import { ActivityType } from '~/generated/graphql'; import { ActivityType } from '~/generated/graphql';
@ -11,27 +11,31 @@ import {
import { useOpenCreateActivityDrawer } from './useOpenCreateActivityDrawer'; import { useOpenCreateActivityDrawer } from './useOpenCreateActivityDrawer';
export const useOpenCreateActivityDrawerForSelectedRowIds = () => { export const useOpenCreateActivityDrawerForSelectedRowIds = () => {
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
const openCreateActivityDrawer = useOpenCreateActivityDrawer(); const openCreateActivityDrawer = useOpenCreateActivityDrawer();
return ( return useRecoilCallback(
type: ActivityType, ({ snapshot }) =>
entityType: ActivityTargetableEntityType, (
relatedEntities?: ActivityTargetableEntity[], type: ActivityType,
) => { entityType: ActivityTargetableEntityType,
let activityTargetableEntityArray: ActivityTargetableEntity[] = relatedEntities?: ActivityTargetableEntity[],
selectedRowIds.map((id) => ({ ) => {
type: entityType, const selectedRowIds = Object.keys(
id, snapshot.getLoadable(selectedRowIdsSelector).getValue(),
})); );
if (relatedEntities) { let activityTargetableEntityArray: ActivityTargetableEntity[] =
activityTargetableEntityArray = selectedRowIds.map((id) => ({
activityTargetableEntityArray.concat(relatedEntities); type: entityType,
} id,
openCreateActivityDrawer({ }));
type, if (relatedEntities) {
targetableEntities: activityTargetableEntityArray, activityTargetableEntityArray =
}); activityTargetableEntityArray.concat(relatedEntities);
}; }
openCreateActivityDrawer({
type,
targetableEntities: activityTargetableEntityArray,
});
},
);
}; };

View File

@ -1,43 +0,0 @@
import { useSetRecoilState } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { IconCheckbox, IconNotes, IconTrash } from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
import { ActivityType } from '~/generated/graphql';
import { useDeleteSelectedComapnies } from './useDeleteCompanies';
export const useCompanyTableActionBarEntries = () => {
const setActionBarEntries = useSetRecoilState(actionBarEntriesState);
const openCreateActivityRightDrawer =
useOpenCreateActivityDrawerForSelectedRowIds();
const handleActivityClick = async (type: ActivityType) => {
openCreateActivityRightDrawer(type, ActivityTargetableEntityType.Company);
};
const deleteSelectedCompanies = useDeleteSelectedComapnies();
return {
setActionBarEntries: () =>
setActionBarEntries([
{
label: 'Note',
Icon: IconNotes,
onClick: () => handleActivityClick(ActivityType.Note),
},
{
label: 'Task',
Icon: IconCheckbox,
onClick: () => handleActivityClick(ActivityType.Task),
},
{
label: 'Delete',
Icon: IconTrash,
accent: 'danger',
onClick: () => deleteSelectedCompanies(),
},
]),
};
};

View File

@ -1,10 +1,10 @@
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { getOperationName } from '@apollo/client/utilities';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection'; import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection';
import { selectedRowIdsSelector } from '@/ui/data/data-table/states/selectors/selectedRowIdsSelector'; import { selectedRowIdsSelector } from '@/ui/data/data-table/states/selectors/selectedRowIdsSelector';
import { tableRowIdsState } from '@/ui/data/data-table/states/tableRowIdsState';
import { import {
IconCheckbox, IconCheckbox,
IconHeart, IconHeart,
@ -12,58 +12,103 @@ import {
IconNotes, IconNotes,
IconTrash, IconTrash,
} from '@/ui/display/icon'; } from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
import { ActivityType, useGetFavoritesQuery } from '~/generated/graphql'; import {
ActivityType,
useDeleteManyCompaniesMutation,
useGetFavoritesQuery,
} from '~/generated/graphql';
import { useDeleteSelectedComapnies } from './useDeleteCompanies'; import { GET_COMPANY } from '../graphql/queries/getCompany';
import { useCreateActivityForCompany } from './useCreateActivityForCompany';
export const useCompanyTableContextMenuEntries = () => { export const useCompanyTableContextMenuEntries = () => {
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState); const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
const createActivityForCompany = useCreateActivityForCompany();
const openCreateActivityRightDrawer = const setTableRowIds = useSetRecoilState(tableRowIdsState);
useOpenCreateActivityDrawerForSelectedRowIds();
const handleButtonClick = async (type: ActivityType) => {
openCreateActivityRightDrawer(type, ActivityTargetableEntityType.Company);
};
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
const selectedCompanyId =
selectedRowIds.length === 1 ? selectedRowIds[0] : '';
const { insertCompanyFavorite, deleteCompanyFavorite } = useFavorites();
const resetRowSelection = useResetTableRowSelection(); const resetRowSelection = useResetTableRowSelection();
const { data } = useGetFavoritesQuery(); const { data } = useGetFavoritesQuery();
const favorites = data?.findFavorites; const favorites = data?.findFavorites;
const { insertCompanyFavorite, deleteCompanyFavorite } = useFavorites();
const isFavorite = const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => {
!!selectedCompanyId && const selectedRowIds = snapshot
!!favorites?.find((favorite) => favorite.company?.id === selectedCompanyId); .getLoadable(selectedRowIdsSelector)
.getValue();
const selectedCompanyId =
selectedRowIds.length === 1 ? selectedRowIds[0] : '';
const isFavorite =
!!selectedCompanyId &&
!!favorites?.find(
(favorite) => favorite.company?.id === selectedCompanyId,
);
const handleFavoriteButtonClick = () => {
resetRowSelection(); resetRowSelection();
if (isFavorite) deleteCompanyFavorite(selectedCompanyId); if (isFavorite) deleteCompanyFavorite(selectedCompanyId);
else insertCompanyFavorite(selectedCompanyId); else insertCompanyFavorite(selectedCompanyId);
}; });
const deleteSelectedCompanies = useDeleteSelectedComapnies(); const [deleteManyCompany] = useDeleteManyCompaniesMutation({
refetchQueries: [getOperationName(GET_COMPANY) ?? ''],
});
const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => {
const rowIdsToDelete = snapshot
.getLoadable(selectedRowIdsSelector)
.getValue();
resetRowSelection();
await deleteManyCompany({
variables: {
ids: rowIdsToDelete,
},
optimisticResponse: {
__typename: 'Mutation',
deleteManyCompany: {
count: rowIdsToDelete.length,
},
},
update: () => {
setTableRowIds((tableRowIds) =>
tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
);
},
});
});
return { return {
setContextMenuEntries: () => setContextMenuEntries: useRecoilCallback(({ snapshot }) => () => {
const selectedRowIds = snapshot
.getLoadable(selectedRowIdsSelector)
.getValue();
const selectedCompanyId =
selectedRowIds.length === 1 ? selectedRowIds[0] : '';
const isFavorite =
!!selectedCompanyId &&
!!favorites?.find(
(favorite) => favorite.company?.id === selectedCompanyId,
);
setContextMenuEntries([ setContextMenuEntries([
{ {
label: 'New task', label: 'New task',
Icon: IconCheckbox, Icon: IconCheckbox,
onClick: () => handleButtonClick(ActivityType.Task), onClick: () => createActivityForCompany(ActivityType.Task),
}, },
{ {
label: 'New note', label: 'New note',
Icon: IconNotes, Icon: IconNotes,
onClick: () => handleButtonClick(ActivityType.Note), onClick: () => createActivityForCompany(ActivityType.Note),
}, },
...(!!selectedCompanyId ...(!!selectedCompanyId
? [ ? [
@ -80,8 +125,29 @@ export const useCompanyTableContextMenuEntries = () => {
label: 'Delete', label: 'Delete',
Icon: IconTrash, Icon: IconTrash,
accent: 'danger', accent: 'danger',
onClick: () => deleteSelectedCompanies(), onClick: () => handleDeleteClick(),
}, },
]), ]);
}),
setActionBarEntries: useRecoilCallback(() => () => {
setActionBarEntriesState([
{
label: 'Task',
Icon: IconCheckbox,
onClick: () => createActivityForCompany(ActivityType.Task),
},
{
label: 'Note',
Icon: IconNotes,
onClick: () => createActivityForCompany(ActivityType.Note),
},
{
label: 'Delete',
Icon: IconTrash,
accent: 'danger',
onClick: () => handleDeleteClick(),
},
]);
}),
}; };
}; };

View File

@ -0,0 +1,17 @@
import { useRecoilCallback } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { ActivityType } from '~/generated/graphql';
export const useCreateActivityForCompany = () => {
const openCreateActivityRightDrawer =
useOpenCreateActivityDrawerForSelectedRowIds();
return useRecoilCallback(
() => (type: ActivityType) => {
openCreateActivityRightDrawer(type, ActivityTargetableEntityType.Company);
},
[openCreateActivityRightDrawer],
);
};

View File

@ -3,7 +3,6 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { companiesAvailableFieldDefinitions } from '@/companies/constants/companiesAvailableFieldDefinitions'; import { companiesAvailableFieldDefinitions } from '@/companies/constants/companiesAvailableFieldDefinitions';
import { getCompaniesOptimisticEffectDefinition } from '@/companies/graphql/optimistic-effect-definitions/getCompaniesOptimisticEffectDefinition'; import { getCompaniesOptimisticEffectDefinition } from '@/companies/graphql/optimistic-effect-definitions/getCompaniesOptimisticEffectDefinition';
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries'; import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport'; import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
import { DataTable } from '@/ui/data/data-table/components/DataTable'; import { DataTable } from '@/ui/data/data-table/components/DataTable';
@ -63,8 +62,8 @@ export const CompanyTable = () => {
viewScopeId, viewScopeId,
}); });
const { setContextMenuEntries } = useCompanyTableContextMenuEntries(); const { setContextMenuEntries, setActionBarEntries } =
const { setActionBarEntries } = useCompanyTableActionBarEntries(); useCompanyTableContextMenuEntries();
const updateCompany = async ( const updateCompany = async (
variables: UpdateOneCompanyMutationVariables, variables: UpdateOneCompanyMutationVariables,

View File

@ -40,24 +40,6 @@ const CompanyTableEffect = () => {
setViewType, setViewType,
]); ]);
// useEffect(() => {
// if (currentViewSorts) {
// setTableSorts(currentViewSorts);
// }
// }, [currentViewFields, currentViewSorts, setTableColumns, setTableSorts]);
// useEffect(() => {
// if (currentViewFilters) {
// setTableFilters(currentViewFilters);
// }
// }, [
// currentViewFields,
// currentViewFilters,
// setTableColumns,
// setTableFilters,
// setTableSorts,
// ]);
return <></>; return <></>;
}; };

View File

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

View File

@ -1,15 +1,35 @@
import styled from '@emotion/styled';
import { DataTable } from '@/ui/data/data-table/components/DataTable'; import { DataTable } from '@/ui/data/data-table/components/DataTable';
import { TableOptionsDropdownId } from '@/ui/data/data-table/constants/TableOptionsDropdownId';
import { TableOptionsDropdown } from '@/ui/data/data-table/options/components/TableOptionsDropdown';
import { ViewBar } from '@/views/components/ViewBar';
import { ViewScope } from '@/views/scopes/ViewScope'; import { ViewScope } from '@/views/scopes/ViewScope';
import { useUpdateOneCompanyMutation } from '~/generated/graphql'; import { useUpdateOneCompanyMutation } from '~/generated/graphql';
import CompanyTableEffect from './CompanyTableEffect';
import { CompanyTableMockDataEffect } from './CompanyTableMockDataEffect'; import { CompanyTableMockDataEffect } from './CompanyTableMockDataEffect';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
export const CompanyTableMockMode = () => { export const CompanyTableMockMode = () => {
return ( return (
<ViewScope viewScopeId="company-table-mock-mode"> <StyledContainer>
<CompanyTableMockDataEffect /> <ViewScope viewScopeId="company-table-mock-mode">
<CompanyTableEffect />
<CompanyTableMockDataEffect />
<ViewBar
optionsDropdownButton={<TableOptionsDropdown />}
optionsDropdownScopeId={TableOptionsDropdownId}
/>
<DataTable updateEntityMutation={useUpdateOneCompanyMutation} /> <DataTable updateEntityMutation={useUpdateOneCompanyMutation} />
</ViewScope> </ViewScope>
</StyledContainer>
); );
}; };

View File

@ -1,6 +1,14 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { tableFiltersScopedState } from '@/ui/data/data-table/states/tableFiltersScopedState';
import { tableSortsScopedState } from '@/ui/data/data-table/states/tableSortsScopedState';
import { turnFiltersIntoWhereClauseV2 } from '@/ui/data/filter/utils/turnFiltersIntoWhereClauseV2';
import { turnSortsIntoOrderByV2 } from '@/ui/data/sort/utils/turnSortsIntoOrderByV2';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useFindManyObjects } from '../hooks/useFindManyObjects'; import { useFindManyObjects } from '../hooks/useFindManyObjects';
import { useMetadataObjectInContext } from '../hooks/useMetadataObjectInContext';
import { useSetObjectDataTableData } from '../hooks/useSetDataTableData'; import { useSetObjectDataTableData } from '../hooks/useSetDataTableData';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier'; import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
@ -9,14 +17,33 @@ export type ObjectDataTableEffectProps = Pick<
'objectNamePlural' 'objectNamePlural'
>; >;
// TODO: merge in a single effect component // This should be migrated to DataTable at some point
export const ObjectDataTableEffect = ({ export const ObjectDataTableEffect = ({
objectNamePlural, objectNamePlural,
}: ObjectDataTableEffectProps) => { }: ObjectDataTableEffectProps) => {
const setDataTableData = useSetObjectDataTableData(); const setDataTableData = useSetObjectDataTableData();
const { foundMetadataObject } = useMetadataObjectInContext();
const tableFilters = useRecoilScopedValue(
tableFiltersScopedState,
TableRecoilScopeContext,
);
const tableSorts = useRecoilScopedValue(
tableSortsScopedState,
TableRecoilScopeContext,
);
const { objects, loading } = useFindManyObjects({ const { objects, loading } = useFindManyObjects({
objectNamePlural, objectNamePlural: objectNamePlural,
filter: turnFiltersIntoWhereClauseV2(
tableFilters,
foundMetadataObject?.fields ?? [],
),
orderBy: turnSortsIntoOrderByV2(
tableSorts,
foundMetadataObject?.fields ?? [],
),
}); });
useEffect(() => { useEffect(() => {

View File

@ -9,6 +9,7 @@ import { InlineCell } from '@/ui/data/inline-cell/components/InlineCell';
import { PropertyBox } from '@/ui/data/inline-cell/property-box/components/PropertyBox'; import { PropertyBox } from '@/ui/data/inline-cell/property-box/components/PropertyBox';
import { InlineCellHotkeyScope } from '@/ui/data/inline-cell/types/InlineCellHotkeyScope'; import { InlineCellHotkeyScope } from '@/ui/data/inline-cell/types/InlineCellHotkeyScope';
import { IconBuildingSkyscraper } from '@/ui/display/icon'; import { IconBuildingSkyscraper } from '@/ui/display/icon';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import { PageBody } from '@/ui/layout/page/PageBody'; import { PageBody } from '@/ui/layout/page/PageBody';
import { PageContainer } from '@/ui/layout/page/PageContainer'; import { PageContainer } from '@/ui/layout/page/PageContainer';
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton'; import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
@ -33,6 +34,8 @@ export const ObjectShowPage = () => {
objectId: string; objectId: string;
}>(); }>();
const { icons } = useLazyLoadIcons();
const { foundMetadataObject } = useFindOneMetadataObject({ const { foundMetadataObject } = useFindOneMetadataObject({
objectNameSingular, objectNameSingular,
}); });
@ -130,6 +133,7 @@ export const ObjectShowPage = () => {
field: metadataField, field: metadataField,
position: index, position: index,
metadataObject: foundMetadataObject, metadataObject: foundMetadataObject,
icons,
}), }),
useUpdateEntityMutation: useUpdateOneObjectMutation, useUpdateEntityMutation: useUpdateOneObjectMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell, hotkeyScope: InlineCellHotkeyScope.InlineCell,

View File

@ -1,11 +1,23 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { DataTable } from '@/ui/data/data-table/components/DataTable'; import { DataTable } from '@/ui/data/data-table/components/DataTable';
import { TableOptionsDropdownId } from '@/ui/data/data-table/constants/TableOptionsDropdownId';
import { TableContext } from '@/ui/data/data-table/contexts/TableContext'; import { TableContext } from '@/ui/data/data-table/contexts/TableContext';
import { TableOptionsDropdown } from '@/ui/data/data-table/options/components/TableOptionsDropdown'; 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 { 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 { 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 { useMetadataObjectInContext } from '../hooks/useMetadataObjectInContext';
import { useUpdateOneObject } from '../hooks/useUpdateOneObject'; import { useUpdateOneObject } from '../hooks/useUpdateOneObject';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier'; import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
@ -28,9 +40,26 @@ export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => {
const { updateOneObject } = useUpdateOneObject({ const { updateOneObject } = useUpdateOneObject({
objectNamePlural, objectNamePlural,
}); });
const { columnDefinitions, foundMetadataObject } =
useMetadataObjectInContext();
const tableScopeId = foundMetadataObject?.namePlural ?? '';
const viewScopeId = objectNamePlural ?? ''; const viewScopeId = objectNamePlural ?? '';
const { persistViewFields } = useViewFields(viewScopeId);
const { setCurrentViewFields } = useView({
viewScopeId,
});
const setTableColumns = useSetRecoilState(
tableColumnsScopedState(tableScopeId),
);
const setTableFilters = useSetRecoilState(
tableFiltersScopedState(tableScopeId),
);
const setTableSorts = useSetRecoilState(tableSortsScopedState(tableScopeId));
const updateEntity = ({ const updateEntity = ({
variables, variables,
}: { }: {
@ -48,16 +77,32 @@ export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => {
}; };
return ( return (
<ViewScope viewScopeId={viewScopeId} onViewFieldsChange={() => {}}> <ViewScope
viewScopeId={viewScopeId}
onViewFieldsChange={(viewFields) => {
setTableColumns(
viewFieldsToColumnDefinitions(viewFields, columnDefinitions),
);
}}
onViewFiltersChange={(viewFilters) => {
setTableFilters(viewFiltersToFilters(viewFilters));
}}
onViewSortsChange={(viewSorts) => {
setTableSorts(viewSortsToSorts(viewSorts));
}}
>
<StyledContainer> <StyledContainer>
<TableContext.Provider <TableContext.Provider
value={{ value={{
onColumnsChange: () => {}, onColumnsChange: useRecoilCallback(() => (columns) => {
setCurrentViewFields?.(columnDefinitionsToViewFields(columns));
persistViewFields(columnDefinitionsToViewFields(columns));
}),
}} }}
> >
<ViewBar <ViewBar
optionsDropdownButton={<TableOptionsDropdown />} optionsDropdownButton={<TableOptionsDropdown />}
optionsDropdownScopeId="table-dropdown-option" optionsDropdownScopeId={TableOptionsDropdownId}
/> />
<ObjectTableEffect /> <ObjectTableEffect />
<ObjectDataTableEffect objectNamePlural={objectNamePlural} /> <ObjectDataTableEffect objectNamePlural={objectNamePlural} />

View File

@ -16,69 +16,43 @@ export const ObjectTableEffect = () => {
setViewObjectId, setViewObjectId,
} = useView(); } = useView();
// const [, setTableColumns] = useRecoilScopedState( const {
// tableColumnsScopedState, columnDefinitions,
// TableRecoilScopeContext, filterDefinitions,
// ); sortDefinitions,
foundMetadataObject,
} = useMetadataObjectInContext();
// const [, setTableSorts] = useRecoilScopedState( const tableScopeId = foundMetadataObject?.namePlural ?? '';
// tableSortsScopedState,
// TableRecoilScopeContext,
// );
// const [, setTableFilters] = useRecoilScopedState(
// tableFiltersScopedState,
// TableRecoilScopeContext,
// );
const { columnDefinitions, objectNamePlural } = useMetadataObjectInContext();
const setAvailableTableColumns = useSetRecoilState( const setAvailableTableColumns = useSetRecoilState(
availableTableColumnsScopedState(objectNamePlural ?? ''), availableTableColumnsScopedState(tableScopeId),
); );
useEffect(() => { useEffect(() => {
setAvailableSortDefinitions?.([]); // TODO: extract from metadata fields if (!foundMetadataObject) {
setAvailableFilterDefinitions?.([]); // TODO: extract from metadata fields return;
setAvailableFieldDefinitions?.(columnDefinitions); }
setViewObjectId?.(objectNamePlural); setViewObjectId?.(foundMetadataObject.id);
setViewType?.(ViewType.Table); setViewType?.(ViewType.Table);
setAvailableSortDefinitions?.(sortDefinitions);
setAvailableFilterDefinitions?.(filterDefinitions);
setAvailableFieldDefinitions?.(columnDefinitions);
setAvailableTableColumns(columnDefinitions); setAvailableTableColumns(columnDefinitions);
}, [ }, [
setAvailableTableColumns, setAvailableTableColumns,
setViewObjectId, setViewObjectId,
setViewType, setViewType,
columnDefinitions, columnDefinitions,
objectNamePlural,
setAvailableSortDefinitions, setAvailableSortDefinitions,
setAvailableFilterDefinitions, setAvailableFilterDefinitions,
setAvailableFieldDefinitions, setAvailableFieldDefinitions,
foundMetadataObject,
sortDefinitions,
filterDefinitions,
]); ]);
// useEffect(() => {
// if (currentViewFields) {
// setTableColumns([...currentViewFields].sort((a, b) => a.index - b.index));
// }
// }, [currentViewFields, setTableColumns]);
// useEffect(() => {
// if (currentViewSorts) {
// setTableSorts(currentViewSorts);
// }
// }, [currentViewFields, currentViewSorts, setTableColumns, setTableSorts]);
// useEffect(() => {
// if (currentViewFilters) {
// setTableFilters(currentViewFilters);
// }
// }, [
// currentViewFields,
// currentViewFilters,
// setTableColumns,
// setTableFilters,
// setTableSorts,
// ]);
return <></>; return <></>;
}; };

View File

@ -49,9 +49,7 @@ export const ObjectTablePage = () => {
}); });
const handleAddButtonClick = async () => { const handleAddButtonClick = async () => {
createOneObject?.({ createOneObject?.({});
name: 'Test',
});
}; };
return ( return (

View File

@ -2,9 +2,14 @@ import { gql } from '@apollo/client';
import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata'; import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { SortDefinition } from '@/ui/data/sort/types/SortDefinition';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier'; import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
import { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition'; import { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition';
import { formatMetadataFieldAsFilterDefinition } from '../utils/formatMetadataFieldAsFilterDefinition';
import { formatMetadataFieldAsSortDefinition } from '../utils/formatMetadataFieldAsSortDefinition';
import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation'; import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation';
import { generateDeleteOneObjectMutation } from '../utils/generateDeleteOneObjectMutation'; import { generateDeleteOneObjectMutation } from '../utils/generateDeleteOneObjectMutation';
import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery'; import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery';
@ -25,6 +30,8 @@ export const useFindOneMetadataObject = ({
object.nameSingular === objectNameSingular, object.nameSingular === objectNameSingular,
); );
const { icons } = useLazyLoadIcons();
const objectNotFoundInMetadata = const objectNotFoundInMetadata =
metadataObjects.length === 0 || metadataObjects.length === 0 ||
(metadataObjects.length > 0 && !foundMetadataObject); (metadataObjects.length > 0 && !foundMetadataObject);
@ -35,6 +42,23 @@ export const useFindOneMetadataObject = ({
position: index, position: index,
field, field,
metadataObject: foundMetadataObject, metadataObject: foundMetadataObject,
icons,
}),
) ?? [];
const filterDefinitions: FilterDefinition[] =
foundMetadataObject?.fields.map((field) =>
formatMetadataFieldAsFilterDefinition({
field,
icons,
}),
) ?? [];
const sortDefinitions: SortDefinition[] =
foundMetadataObject?.fields.map((field) =>
formatMetadataFieldAsSortDefinition({
field,
icons,
}), }),
) ?? []; ) ?? [];
@ -93,6 +117,8 @@ export const useFindOneMetadataObject = ({
foundMetadataObject, foundMetadataObject,
objectNotFoundInMetadata, objectNotFoundInMetadata,
columnDefinitions, columnDefinitions,
filterDefinitions,
sortDefinitions,
findManyQuery, findManyQuery,
findOneQuery, findOneQuery,
createOneMutation, createOneMutation,

View File

@ -13,15 +13,22 @@ export const useMetadataObjectInContext = () => {
); );
} }
const { foundMetadataObject, loading, columnDefinitions } = const {
useFindOneMetadataObject({ foundMetadataObject,
objectNamePlural: context.objectNamePlural, loading,
}); columnDefinitions,
filterDefinitions,
sortDefinitions,
} = useFindOneMetadataObject({
objectNamePlural: context.objectNamePlural,
});
return { return {
...context, ...context,
foundMetadataObject, foundMetadataObject,
loading, loading,
columnDefinitions, columnDefinitions,
filterDefinitions,
sortDefinitions,
}; };
}; };

View File

@ -3,16 +3,13 @@ import { useRecoilCallback } from 'recoil';
import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection'; import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection';
import { isFetchingDataTableDataState } from '@/ui/data/data-table/states/isFetchingDataTableDataState'; import { isFetchingDataTableDataState } from '@/ui/data/data-table/states/isFetchingDataTableDataState';
import { numberOfTableRowsState } from '@/ui/data/data-table/states/numberOfTableRowsState'; import { numberOfTableRowsState } from '@/ui/data/data-table/states/numberOfTableRowsState';
import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { tableRowIdsState } from '@/ui/data/data-table/states/tableRowIdsState'; import { tableRowIdsState } from '@/ui/data/data-table/states/tableRowIdsState';
import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFamilyState'; import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFamilyState';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId'; import { useView } from '@/views/hooks/useView';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
export const useSetObjectDataTableData = () => { export const useSetObjectDataTableData = () => {
const resetTableRowSelection = useResetTableRowSelection(); const resetTableRowSelection = useResetTableRowSelection();
const { setEntityCountInCurrentView } = useView();
const tableContextScopeId = useRecoilScopeId(TableRecoilScopeContext);
return useRecoilCallback( return useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
@ -40,14 +37,10 @@ export const useSetObjectDataTableData = () => {
resetTableRowSelection(); resetTableRowSelection();
set(numberOfTableRowsState, entityIds.length); set(numberOfTableRowsState, entityIds.length);
setEntityCountInCurrentView(entityIds.length);
set(
availableSortDefinitionsScopedState({ scopeId: tableContextScopeId }),
[],
);
set(isFetchingDataTableDataState, false); set(isFetchingDataTableDataState, false);
}, },
[resetTableRowSelection, tableContextScopeId], [resetTableRowSelection, setEntityCountInCurrentView],
); );
}; };

View File

@ -1,30 +1,20 @@
import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition';
import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata'; import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata';
import { FieldType } from '@/ui/data/field/types/FieldType';
import { IconBrandLinkedin } from '@/ui/display/icon';
import { MetadataObject } from '../types/MetadataObject'; import { MetadataObject } from '../types/MetadataObject';
const parseFieldType = (fieldType: string): FieldType => { import { parseFieldType } from './parseFieldType';
if (fieldType === 'url') {
return 'urlV2';
}
if (fieldType === 'money') {
return 'moneyAmountV2';
}
return fieldType as FieldType;
};
export const formatMetadataFieldAsColumnDefinition = ({ export const formatMetadataFieldAsColumnDefinition = ({
position, position,
field, field,
metadataObject, metadataObject,
icons,
}: { }: {
position: number; position: number;
field: MetadataObject['fields'][0]; field: MetadataObject['fields'][0];
metadataObject: Omit<MetadataObject, 'fields'>; metadataObject: Omit<MetadataObject, 'fields'>;
icons: Record<string, any>;
}): ColumnDefinition<FieldMetadata> => ({ }): ColumnDefinition<FieldMetadata> => ({
position, position,
fieldId: field.id, fieldId: field.id,
@ -35,7 +25,7 @@ export const formatMetadataFieldAsColumnDefinition = ({
fieldName: field.name, fieldName: field.name,
placeHolder: field.label, placeHolder: field.label,
}, },
Icon: IconBrandLinkedin, Icon: icons[field.icon ?? 'Icon123'],
isVisible: true, isVisible: true,
basePathToShowPage: `/object/${metadataObject.nameSingular}/`, basePathToShowPage: `/object/${metadataObject.nameSingular}/`,
}); });

View File

@ -0,0 +1,16 @@
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { MetadataObject } from '../types/MetadataObject';
export const formatMetadataFieldAsFilterDefinition = ({
field,
icons,
}: {
field: MetadataObject['fields'][0];
icons: Record<string, any>;
}): FilterDefinition => ({
fieldId: field.id,
label: field.label,
Icon: icons[field.icon ?? 'Icon123'],
type: 'text',
});

View File

@ -0,0 +1,15 @@
import { SortDefinition } from '@/ui/data/sort/types/SortDefinition';
import { MetadataObject } from '../types/MetadataObject';
export const formatMetadataFieldAsSortDefinition = ({
field,
icons,
}: {
field: MetadataObject['fields'][0];
icons: Record<string, any>;
}): SortDefinition => ({
fieldId: field.id,
label: field.label,
Icon: icons[field.icon ?? 'Icon123'],
});

View File

@ -0,0 +1,13 @@
import { FieldType } from '@/ui/data/field/types/FieldType';
export const parseFieldType = (fieldType: string): FieldType => {
if (fieldType === 'url') {
return 'urlV2';
}
if (fieldType === 'money') {
return 'moneyAmountV2';
}
return fieldType as FieldType;
};

View File

@ -1,4 +1,4 @@
import { useRecoilCallback, useRecoilValue } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds'; import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { import {
@ -12,12 +12,14 @@ import { ActivityType, Person } from '~/generated/graphql';
export const useCreateActivityForPeople = () => { export const useCreateActivityForPeople = () => {
const openCreateActivityRightDrawer = const openCreateActivityRightDrawer =
useOpenCreateActivityDrawerForSelectedRowIds(); useOpenCreateActivityDrawerForSelectedRowIds();
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
return useRecoilCallback( return useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(type: ActivityType) => { (type: ActivityType) => {
const relatedEntites: ActivityTargetableEntity[] = []; const relatedEntites: ActivityTargetableEntity[] = [];
const selectedRowIds = Object.keys(
snapshot.getLoadable(selectedRowIdsSelector).getValue(),
);
for (const id of selectedRowIds) { for (const id of selectedRowIds) {
const person = snapshot const person = snapshot
.getLoadable(entityFieldsFamilyState(id)) .getLoadable(entityFieldsFamilyState(id))
@ -39,6 +41,6 @@ export const useCreateActivityForPeople = () => {
relatedEntites, relatedEntites,
); );
}, },
[selectedRowIds, openCreateActivityRightDrawer], [openCreateActivityRightDrawer],
); );
}; };

View File

@ -1,75 +0,0 @@
import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection';
import { selectedRowIdsSelector } from '@/ui/data/data-table/states/selectors/selectedRowIdsSelector';
import { tableRowIdsState } from '@/ui/data/data-table/states/tableRowIdsState';
import { IconCheckbox, IconNotes, IconTrash } from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
import { ActivityType, useDeleteManyPersonMutation } from '~/generated/graphql';
import { GET_PEOPLE } from '../graphql/queries/getPeople';
import { useCreateActivityForPeople } from './useCreateActivityForPeople';
export const usePersonTableActionBarEntries = () => {
const selectedRowIds = useRecoilValue(selectedRowIdsSelector);
const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
const setActionBarEntries = useSetRecoilState(actionBarEntriesState);
const createActivityForPeople = useCreateActivityForPeople();
const resetRowSelection = useResetTableRowSelection();
const [deleteManyPerson] = useDeleteManyPersonMutation({
refetchQueries: [getOperationName(GET_PEOPLE) ?? ''],
});
const handleDeleteClick = async () => {
const rowIdsToDelete = selectedRowIds;
resetRowSelection();
await deleteManyPerson({
variables: {
ids: rowIdsToDelete,
},
optimisticResponse: {
__typename: 'Mutation',
deleteManyPerson: {
count: rowIdsToDelete.length,
},
},
update: (cache) => {
setTableRowIds(
tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
);
rowIdsToDelete.forEach((id) => {
cache.evict({ id: cache.identify({ id, __typename: 'Person' }) });
cache.gc();
});
},
});
};
return {
setActionBarEntries: () =>
setActionBarEntries([
{
label: 'Note',
Icon: IconNotes,
onClick: () => createActivityForPeople(ActivityType.Note),
},
{
label: 'Task',
Icon: IconCheckbox,
onClick: () => createActivityForPeople(ActivityType.Task),
},
{
label: 'Delete',
Icon: IconTrash,
accent: 'danger',
onClick: () => handleDeleteClick(),
},
]),
};
};

View File

@ -1,5 +1,5 @@
import { getOperationName } from '@apollo/client/utilities'; import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection'; import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection';
@ -12,6 +12,7 @@ import {
IconNotes, IconNotes,
IconTrash, IconTrash,
} from '@/ui/display/icon'; } from '@/ui/display/icon';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
import { import {
ActivityType, ActivityType,
@ -25,38 +26,41 @@ import { useCreateActivityForPeople } from './useCreateActivityForPeople';
export const usePersonTableContextMenuEntries = () => { export const usePersonTableContextMenuEntries = () => {
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState); const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
const createActivityForPeople = useCreateActivityForPeople(); const createActivityForPeople = useCreateActivityForPeople();
const selectedRowIds = useRecoilValue(selectedRowIdsSelector); const setTableRowIds = useSetRecoilState(tableRowIdsState);
const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState);
const resetRowSelection = useResetTableRowSelection(); const resetRowSelection = useResetTableRowSelection();
const selectedPersonId = selectedRowIds.length === 1 ? selectedRowIds[0] : '';
const { data } = useGetFavoritesQuery(); const { data } = useGetFavoritesQuery();
const favorites = data?.findFavorites; const favorites = data?.findFavorites;
const isFavorite =
!!selectedPersonId &&
!!favorites?.find((favorite) => favorite.person?.id === selectedPersonId);
const { insertPersonFavorite, deletePersonFavorite } = useFavorites(); const { insertPersonFavorite, deletePersonFavorite } = useFavorites();
const handleFavoriteButtonClick = () => { const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => {
const selectedRowIds = snapshot
.getLoadable(selectedRowIdsSelector)
.getValue();
const selectedPersonId =
selectedRowIds.length === 1 ? selectedRowIds[0] : '';
const isFavorite =
!!selectedPersonId &&
!!favorites?.find((favorite) => favorite.person?.id === selectedPersonId);
resetRowSelection(); resetRowSelection();
if (isFavorite) deletePersonFavorite(selectedPersonId); if (isFavorite) deletePersonFavorite(selectedPersonId);
else insertPersonFavorite(selectedPersonId); else insertPersonFavorite(selectedPersonId);
}; });
const [deleteManyPerson] = useDeleteManyPersonMutation({ const [deleteManyPerson] = useDeleteManyPersonMutation({
refetchQueries: [getOperationName(GET_PEOPLE) ?? ''], refetchQueries: [getOperationName(GET_PEOPLE) ?? ''],
}); });
const handleDeleteClick = async () => { const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => {
const rowIdsToDelete = selectedRowIds; const rowIdsToDelete = snapshot
.getLoadable(selectedRowIdsSelector)
.getValue();
resetRowSelection(); resetRowSelection();
@ -71,15 +75,28 @@ export const usePersonTableContextMenuEntries = () => {
}, },
}, },
update: () => { update: () => {
setTableRowIds( setTableRowIds((tableRowIds) =>
tableRowIds.filter((id) => !rowIdsToDelete.includes(id)), tableRowIds.filter((id) => !rowIdsToDelete.includes(id)),
); );
}, },
}); });
}; });
return { return {
setContextMenuEntries: () => setContextMenuEntries: useRecoilCallback(({ snapshot }) => () => {
const selectedRowIds = snapshot
.getLoadable(selectedRowIdsSelector)
.getValue();
const selectedPersonId =
selectedRowIds.length === 1 ? selectedRowIds[0] : '';
const isFavorite =
!!selectedPersonId &&
!!favorites?.find(
(favorite) => favorite.person?.id === selectedPersonId,
);
setContextMenuEntries([ setContextMenuEntries([
{ {
label: 'New task', label: 'New task',
@ -108,6 +125,27 @@ export const usePersonTableContextMenuEntries = () => {
accent: 'danger', accent: 'danger',
onClick: () => handleDeleteClick(), onClick: () => handleDeleteClick(),
}, },
]), ]);
}),
setActionBarEntries: useRecoilCallback(() => () => {
setActionBarEntriesState([
{
label: 'Task',
Icon: IconCheckbox,
onClick: () => createActivityForPeople(ActivityType.Task),
},
{
label: 'Note',
Icon: IconNotes,
onClick: () => createActivityForPeople(ActivityType.Note),
},
{
label: 'Delete',
Icon: IconTrash,
accent: 'danger',
onClick: () => handleDeleteClick(),
},
]);
}),
}; };
}; };

View File

@ -3,7 +3,6 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { peopleAvailableFieldDefinitions } from '@/people/constants/peopleAvailableFieldDefinitions'; import { peopleAvailableFieldDefinitions } from '@/people/constants/peopleAvailableFieldDefinitions';
import { getPeopleOptimisticEffectDefinition } from '@/people/graphql/optimistic-effect-definitions/getPeopleOptimisticEffectDefinition'; import { getPeopleOptimisticEffectDefinition } from '@/people/graphql/optimistic-effect-definitions/getPeopleOptimisticEffectDefinition';
import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableActionBarEntries';
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePersonTableContextMenuEntries'; import { usePersonTableContextMenuEntries } from '@/people/hooks/usePersonTableContextMenuEntries';
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport'; import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
import { DataTable } from '@/ui/data/data-table/components/DataTable'; import { DataTable } from '@/ui/data/data-table/components/DataTable';
@ -53,8 +52,8 @@ export const PersonTable = () => {
viewScopeId, viewScopeId,
}); });
const { setContextMenuEntries } = usePersonTableContextMenuEntries(); const { setContextMenuEntries, setActionBarEntries } =
const { setActionBarEntries } = usePersonTableActionBarEntries(); usePersonTableContextMenuEntries();
const updatePerson = async (variables: UpdateOnePersonMutationVariables) => { const updatePerson = async (variables: UpdateOnePersonMutationVariables) => {
updateEntityMutation({ updateEntityMutation({
@ -74,7 +73,6 @@ export const PersonTable = () => {
const StyledContainer = styled.div` const StyledContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
overflow: auto; overflow: auto;
`; `;

View File

@ -46,29 +46,6 @@ const PeopleTableEffect = () => {
setViewObjectId, setViewObjectId,
setViewType, setViewType,
]); ]);
// useEffect(() => {
// if (currentViewFields) {
// setTableColumns([...currentViewFields].sort((a, b) => a.index - b.index));
// }
// }, [currentViewFields, setTableColumns]);
// useEffect(() => {
// if (currentViewSorts) {
// setTableSorts(currentViewSorts);
// }
// }, [currentViewFields, currentViewSorts, setTableColumns, setTableSorts]);
// useEffect(() => {
// if (currentViewFilters) {
// setTableFilters(currentViewFilters);
// }
// }, [
// currentViewFields,
// currentViewFilters,
// setTableColumns,
// setTableFilters,
// setTableSorts,
// ]);
return <></>; return <></>;
}; };

View File

@ -79,6 +79,7 @@ const StyledTableContainer = styled.div`
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
position: relative;
`; `;
type DataTableProps = { type DataTableProps = {
@ -121,20 +122,20 @@ export const DataTable = ({ updateEntityMutation }: DataTableProps) => {
return ( return (
<EntityUpdateMutationContext.Provider value={updateEntityMutation}> <EntityUpdateMutationContext.Provider value={updateEntityMutation}>
<StyledTableWithHeader> <StyledTableWithHeader>
<StyledTableContainer ref={tableBodyRef}> <StyledTableContainer>
<ScrollWrapper> <ScrollWrapper>
<div> <div ref={tableBodyRef}>
<StyledTable className="entity-table-cell"> <StyledTable className="entity-table-cell">
<DataTableHeader /> <DataTableHeader />
<DataTableBody /> <DataTableBody />
</StyledTable> </StyledTable>
<DragSelect
dragSelectable={tableBodyRef}
onDragSelectionStart={resetTableRowSelection}
onDragSelectionChange={setRowSelectedState}
/>
</div> </div>
</ScrollWrapper> </ScrollWrapper>
<DragSelect
dragSelectable={tableBodyRef}
onDragSelectionStart={resetTableRowSelection}
onDragSelectionChange={setRowSelectedState}
/>
</StyledTableContainer> </StyledTableContainer>
</StyledTableWithHeader> </StyledTableWithHeader>
</EntityUpdateMutationContext.Provider> </EntityUpdateMutationContext.Provider>

View File

@ -23,10 +23,8 @@ export const DataTableEffect = ({
getRequestResultKey, getRequestResultKey,
getRequestOptimisticEffectDefinition, getRequestOptimisticEffectDefinition,
filterDefinitionArray,
setActionBarEntries, setActionBarEntries,
setContextMenuEntries, setContextMenuEntries,
sortDefinitionArray,
}: { }: {
useGetRequest: typeof useGetCompaniesQuery | typeof useGetPeopleQuery; useGetRequest: typeof useGetCompaniesQuery | typeof useGetPeopleQuery;
getRequestResultKey: string; getRequestResultKey: string;
@ -59,7 +57,7 @@ export const DataTableEffect = ({
onCompleted: (data: any) => { onCompleted: (data: any) => {
const entities = data[getRequestResultKey] ?? []; const entities = data[getRequestResultKey] ?? [];
setDataTableData(entities, filterDefinitionArray, sortDefinitionArray); setDataTableData(entities);
registerOptimisticEffect({ registerOptimisticEffect({
variables: { orderBy: sortsOrderBy, where: tablefiltersWhere }, variables: { orderBy: sortsOrderBy, where: tablefiltersWhere },

View File

@ -1,15 +1,10 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFamilyState'; import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFamilyState';
import { FilterDefinition } from '@/ui/data/filter/types/FilterDefinition';
import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
import { useView } from '@/views/hooks/useView'; import { useView } from '@/views/hooks/useView';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
import { SortDefinition } from '../../sort/types/SortDefinition';
import { isFetchingDataTableDataState } from '../states/isFetchingDataTableDataState'; import { isFetchingDataTableDataState } from '../states/isFetchingDataTableDataState';
import { numberOfTableRowsState } from '../states/numberOfTableRowsState'; import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext';
import { tableRowIdsState } from '../states/tableRowIdsState'; import { tableRowIdsState } from '../states/tableRowIdsState';
import { useResetTableRowSelection } from './useResetTableRowSelection'; import { useResetTableRowSelection } from './useResetTableRowSelection';
@ -18,15 +13,9 @@ export const useSetDataTableData = () => {
const resetTableRowSelection = useResetTableRowSelection(); const resetTableRowSelection = useResetTableRowSelection();
const { setEntityCountInCurrentView } = useView(); const { setEntityCountInCurrentView } = useView();
const tableContextScopeId = useRecoilScopeId(TableRecoilScopeContext);
return useRecoilCallback( return useRecoilCallback(
({ set, snapshot }) => ({ set, snapshot }) =>
<T extends { id: string }>( <T extends { id: string }>(newEntityArray: T[]) => {
newEntityArray: T[],
filterDefinitionArray: FilterDefinition[],
sortDefinitionArray: SortDefinition[],
) => {
for (const entity of newEntityArray) { for (const entity of newEntityArray) {
const currentEntity = snapshot const currentEntity = snapshot
.getLoadable(entityFieldsFamilyState(entity.id)) .getLoadable(entityFieldsFamilyState(entity.id))
@ -50,16 +39,9 @@ export const useSetDataTableData = () => {
resetTableRowSelection(); resetTableRowSelection();
set(numberOfTableRowsState, entityIds.length); set(numberOfTableRowsState, entityIds.length);
setEntityCountInCurrentView(entityIds.length); setEntityCountInCurrentView(entityIds.length);
set(
availableSortDefinitionsScopedState({ scopeId: tableContextScopeId }),
sortDefinitionArray,
);
set(isFetchingDataTableDataState, false); set(isFetchingDataTableDataState, false);
}, },
[resetTableRowSelection, setEntityCountInCurrentView, tableContextScopeId], [resetTableRowSelection, setEntityCountInCurrentView],
); );
}; };

View File

@ -0,0 +1,53 @@
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { Field } from '~/generated/graphql';
import { Filter } from '../types/Filter';
type FilterToTurnIntoWhereClause = Omit<Filter, 'definition'> & {
definition: {
type: Filter['definition']['type'];
};
};
export const turnFiltersIntoWhereClauseV2 = (
filters: FilterToTurnIntoWhereClause[],
fields: Pick<Field, 'id' | 'name'>[],
) => {
const whereClause: Record<string, any> = {};
filters.forEach((filter) => {
const correspondingField = fields.find(
(field) => field.id === filter.fieldId,
);
if (!correspondingField) {
throw new Error(
`Could not find field ${filter.fieldId} in metadata object`,
);
}
switch (filter.definition.type) {
case 'text':
switch (filter.operand) {
case ViewFilterOperand.Contains:
whereClause[correspondingField.name] = {
eq: filter.value,
};
return;
case ViewFilterOperand.DoesNotContain:
whereClause[correspondingField.name] = {
not: {
eq: filter.value,
},
};
return;
default:
throw new Error(
`Unknown operand ${filter.operand} for ${filter.definition.type} filter`,
);
}
default:
throw new Error('Unknown filter type');
}
});
return whereClause;
};

View File

@ -1,6 +1,6 @@
import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2'; import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2';
import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState';
import { availableSortDefinitionsScopedState } from '../states/availableSortDefinitionsScopedState';
import { isSortSelectedScopedState } from '../states/isSortSelectedScopedState'; import { isSortSelectedScopedState } from '../states/isSortSelectedScopedState';
export const useSortStates = (scopeId: string) => { export const useSortStates = (scopeId: string) => {

View File

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

View File

@ -0,0 +1,26 @@
import { Field } from '~/generated/graphql';
import { Sort } from '../types/Sort';
export const turnSortsIntoOrderByV2 = (
sorts: Sort[],
fields: Pick<Field, 'id' | 'name'>[],
) => {
const sortsObject: Record<string, 'AscNullsFirst' | 'DescNullsLast'> = {};
sorts.forEach((sort) => {
const correspondingField = fields.find(
(field) => field.id === sort.fieldId,
);
if (!correspondingField) {
throw new Error(
`Could not find field ${sort.fieldId} in metadata object`,
);
}
const direction =
sort.direction === 'asc' ? 'AscNullsFirst' : 'DescNullsLast';
sortsObject[correspondingField.name] = direction;
});
return sortsObject;
};

View File

@ -42,6 +42,7 @@ const StyledWrapper = styled.div`
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
position: relative;
width: 100%; width: 100%;
`; `;

View File

@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom';
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries'; import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries';
import { CompanyTableMockMode } from '@/companies/table/components/CompanyTableMockMode'; import { CompanyTableMockMode } from '@/companies/table/components/CompanyTableMockMode';
import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
@ -12,7 +12,7 @@ import { actionBarOpenState } from '../../states/actionBarIsOpenState';
import { ActionBar } from '../ActionBar'; import { ActionBar } from '../ActionBar';
const FilledActionBar = (props: { selectedIds: string[] }) => { const FilledActionBar = (props: { selectedIds: string[] }) => {
const { setActionBarEntries } = useCompanyTableActionBarEntries(); const { setActionBarEntries } = useCompanyTableContextMenuEntries();
setActionBarEntries(); setActionBarEntries();
const setActionBarOpenState = useSetRecoilState(actionBarOpenState); const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
setActionBarOpenState(true); setActionBarOpenState(true);

View File

@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useCreateOneObject } from '@/metadata/hooks/useCreateOneObject';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings'; import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
@ -17,6 +18,7 @@ import { H2Title } from '@/ui/display/typography/components/H2Title';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { ViewType } from '@/views/types/ViewType';
export const SettingsNewObject = () => { export const SettingsNewObject = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -29,6 +31,10 @@ export const SettingsNewObject = () => {
disabledMetadataObjects: disabledObjects, disabledMetadataObjects: disabledObjects,
} = useMetadataObjectForSettings(); } = useMetadataObjectForSettings();
const { createOneObject: createOneView } = useCreateOneObject({
objectNamePlural: 'viewsV2',
});
const [selectedStandardObjectIds, setSelectedStandardObjectIds] = useState< const [selectedStandardObjectIds, setSelectedStandardObjectIds] = useState<
Record<string, boolean> Record<string, boolean>
>({}); >({});
@ -60,12 +66,18 @@ export const SettingsNewObject = () => {
} }
if (selectedObjectType === 'Custom') { if (selectedObjectType === 'Custom') {
await createObject({ const createdObject = await createObject({
labelPlural: customFormValues.labelPlural, labelPlural: customFormValues.labelPlural,
labelSingular: customFormValues.labelSingular, labelSingular: customFormValues.labelSingular,
description: customFormValues.description, description: customFormValues.description,
icon: customFormValues.icon, icon: customFormValues.icon,
}); });
await createOneView?.({
objectId: createdObject.data?.createOneObject.id,
type: ViewType.Table,
name: `All ${customFormValues.labelPlural}`,
});
} }
navigate('/settings/objects'); navigate('/settings/objects');

View File

@ -1,8 +1,11 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { useCreateOneObject } from '@/metadata/hooks/useCreateOneObject';
import { useFindManyObjects } from '@/metadata/hooks/useFindManyObjects';
import { useMetadataField } from '@/metadata/hooks/useMetadataField'; import { useMetadataField } from '@/metadata/hooks/useMetadataField';
import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings'; import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings';
import { PaginatedObjectTypeResults } from '@/metadata/types/PaginatedObjectTypeResults';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
@ -13,6 +16,8 @@ import { AppPath } from '@/types/AppPath';
import { IconSettings } from '@/ui/display/icon'; import { IconSettings } from '@/ui/display/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
export const SettingsObjectNewFieldStep2 = () => { export const SettingsObjectNewFieldStep2 = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -36,15 +41,47 @@ export const SettingsObjectNewFieldStep2 = () => {
type: MetadataFieldDataType; type: MetadataFieldDataType;
}>({ icon: 'IconUsers', label: '', type: 'number' }); }>({ icon: 'IconUsers', label: '', type: 'number' });
if (!activeMetadataObject) return null; const [objectViews, setObjectViews] = useState<View[]>([]);
const { createOneObject: createOneViewField } = useCreateOneObject({
objectNamePlural: 'viewFieldsV2',
});
useFindManyObjects({
objectNamePlural: 'viewsV2',
filter: {
type: { eq: ViewType.Table },
objectId: { eq: activeMetadataObject?.id },
},
onCompleted: async (data: PaginatedObjectTypeResults<View>) => {
const views = data.edges;
if (!views) {
return;
}
setObjectViews(data.edges.map(({ node }) => node));
},
});
if (!activeMetadataObject || !objectViews.length) return null;
const canSave = !!formValues.label; const canSave = !!formValues.label;
const handleSave = async () => { const handleSave = async () => {
await createMetadataField({ const createdField = await createMetadataField({
...formValues, ...formValues,
objectId: activeMetadataObject.id, objectId: activeMetadataObject.id,
}); });
objectViews.forEach(async (view) => {
await createOneViewField?.({
viewId: view.id,
fieldId: createdField.data?.createOneField.id,
position: activeMetadataObject.fields.length,
isVisible: true,
size: 100,
});
});
navigate(`/settings/objects/${objectSlug}`); navigate(`/settings/objects/${objectSlug}`);
}; };

View File

@ -1,7 +1,7 @@
import { Command, CommandRunner, Option } from 'nest-commander'; import { Command, CommandRunner, Option } from 'nest-commander';
import { TenantInitialisationService } from '../tenant-initialisation/tenant-initialisation.service'; import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
import { DataSourceMetadataService } from '../data-source-metadata/data-source-metadata.service'; import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service';
// TODO: implement dry-run // TODO: implement dry-run
interface DataSeedTenantOptions { interface DataSeedTenantOptions {

View File

@ -22,7 +22,7 @@ export class BeforeCreateOneField<T extends FieldMetadata>
} }
instance.input.workspaceId = workspaceId; instance.input.workspaceId = workspaceId;
instance.input.isActive = false; instance.input.isActive = true;
instance.input.isCustom = true; instance.input.isCustom = true;
return instance; return instance;
} }

View File

@ -32,7 +32,7 @@ export class BeforeCreateOneObject<T extends ObjectMetadata>
instance.input.dataSourceId = lastDataSourceMetadata.id; instance.input.dataSourceId = lastDataSourceMetadata.id;
instance.input.targetTableName = instance.input.namePlural; instance.input.targetTableName = instance.input.namePlural;
instance.input.workspaceId = workspaceId; instance.input.workspaceId = workspaceId;
instance.input.isActive = false; instance.input.isActive = true;
instance.input.isCustom = true; instance.input.isCustom = true;
return instance; return instance;
} }