diff --git a/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts b/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts index e7e7cb393..40a4a03b3 100644 --- a/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts +++ b/front/src/modules/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds.ts @@ -1,4 +1,4 @@ -import { useRecoilValue } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { selectedRowIdsSelector } from '@/ui/data/data-table/states/selectors/selectedRowIdsSelector'; import { ActivityType } from '~/generated/graphql'; @@ -11,27 +11,31 @@ import { import { useOpenCreateActivityDrawer } from './useOpenCreateActivityDrawer'; export const useOpenCreateActivityDrawerForSelectedRowIds = () => { - const selectedRowIds = useRecoilValue(selectedRowIdsSelector); - const openCreateActivityDrawer = useOpenCreateActivityDrawer(); - return ( - type: ActivityType, - entityType: ActivityTargetableEntityType, - relatedEntities?: ActivityTargetableEntity[], - ) => { - let activityTargetableEntityArray: ActivityTargetableEntity[] = - selectedRowIds.map((id) => ({ - type: entityType, - id, - })); - if (relatedEntities) { - activityTargetableEntityArray = - activityTargetableEntityArray.concat(relatedEntities); - } - openCreateActivityDrawer({ - type, - targetableEntities: activityTargetableEntityArray, - }); - }; + return useRecoilCallback( + ({ snapshot }) => + ( + type: ActivityType, + entityType: ActivityTargetableEntityType, + relatedEntities?: ActivityTargetableEntity[], + ) => { + const selectedRowIds = Object.keys( + snapshot.getLoadable(selectedRowIdsSelector).getValue(), + ); + let activityTargetableEntityArray: ActivityTargetableEntity[] = + selectedRowIds.map((id) => ({ + type: entityType, + id, + })); + if (relatedEntities) { + activityTargetableEntityArray = + activityTargetableEntityArray.concat(relatedEntities); + } + openCreateActivityDrawer({ + type, + targetableEntities: activityTargetableEntityArray, + }); + }, + ); }; diff --git a/front/src/modules/companies/hooks/useCompanyTableActionBarEntries.tsx b/front/src/modules/companies/hooks/useCompanyTableActionBarEntries.tsx deleted file mode 100644 index 910c02dd0..000000000 --- a/front/src/modules/companies/hooks/useCompanyTableActionBarEntries.tsx +++ /dev/null @@ -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(), - }, - ]), - }; -}; diff --git a/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx b/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx index da15a8e96..7d0d7f3bd 100644 --- a/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx +++ b/front/src/modules/companies/hooks/useCompanyTableContextMenuEntries.tsx @@ -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 { 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, IconHeart, @@ -12,58 +12,103 @@ import { IconNotes, IconTrash, } from '@/ui/display/icon'; +import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; 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 = () => { const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState); + const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState); + const createActivityForCompany = useCreateActivityForCompany(); - const openCreateActivityRightDrawer = - 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 setTableRowIds = useSetRecoilState(tableRowIdsState); const resetRowSelection = useResetTableRowSelection(); const { data } = useGetFavoritesQuery(); - const favorites = data?.findFavorites; + const { insertCompanyFavorite, deleteCompanyFavorite } = useFavorites(); - const isFavorite = - !!selectedCompanyId && - !!favorites?.find((favorite) => favorite.company?.id === selectedCompanyId); + const handleFavoriteButtonClick = 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, + ); - const handleFavoriteButtonClick = () => { resetRowSelection(); if (isFavorite) deleteCompanyFavorite(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 { - 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([ { label: 'New task', Icon: IconCheckbox, - onClick: () => handleButtonClick(ActivityType.Task), + onClick: () => createActivityForCompany(ActivityType.Task), }, { label: 'New note', Icon: IconNotes, - onClick: () => handleButtonClick(ActivityType.Note), + onClick: () => createActivityForCompany(ActivityType.Note), }, ...(!!selectedCompanyId ? [ @@ -80,8 +125,29 @@ export const useCompanyTableContextMenuEntries = () => { label: 'Delete', Icon: IconTrash, 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(), + }, + ]); + }), }; }; diff --git a/front/src/modules/companies/hooks/useCreateActivityForCompany.ts b/front/src/modules/companies/hooks/useCreateActivityForCompany.ts new file mode 100644 index 000000000..8cde53be9 --- /dev/null +++ b/front/src/modules/companies/hooks/useCreateActivityForCompany.ts @@ -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], + ); +}; diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index cb19871b2..8414def3a 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -3,7 +3,6 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { companiesAvailableFieldDefinitions } from '@/companies/constants/companiesAvailableFieldDefinitions'; import { getCompaniesOptimisticEffectDefinition } from '@/companies/graphql/optimistic-effect-definitions/getCompaniesOptimisticEffectDefinition'; -import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries'; import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries'; import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport'; import { DataTable } from '@/ui/data/data-table/components/DataTable'; @@ -63,8 +62,8 @@ export const CompanyTable = () => { viewScopeId, }); - const { setContextMenuEntries } = useCompanyTableContextMenuEntries(); - const { setActionBarEntries } = useCompanyTableActionBarEntries(); + const { setContextMenuEntries, setActionBarEntries } = + useCompanyTableContextMenuEntries(); const updateCompany = async ( variables: UpdateOneCompanyMutationVariables, diff --git a/front/src/modules/companies/table/components/CompanyTableEffect.tsx b/front/src/modules/companies/table/components/CompanyTableEffect.tsx index 8e9d72d8a..99c126106 100644 --- a/front/src/modules/companies/table/components/CompanyTableEffect.tsx +++ b/front/src/modules/companies/table/components/CompanyTableEffect.tsx @@ -40,24 +40,6 @@ const CompanyTableEffect = () => { setViewType, ]); - // useEffect(() => { - // if (currentViewSorts) { - // setTableSorts(currentViewSorts); - // } - // }, [currentViewFields, currentViewSorts, setTableColumns, setTableSorts]); - - // useEffect(() => { - // if (currentViewFilters) { - // setTableFilters(currentViewFilters); - // } - // }, [ - // currentViewFields, - // currentViewFilters, - // setTableColumns, - // setTableFilters, - // setTableSorts, - // ]); - return <>; }; diff --git a/front/src/modules/companies/table/components/CompanyTableMockDataEffect.tsx b/front/src/modules/companies/table/components/CompanyTableMockDataEffect.tsx index 0ae168346..85ddb6464 100644 --- a/front/src/modules/companies/table/components/CompanyTableMockDataEffect.tsx +++ b/front/src/modules/companies/table/components/CompanyTableMockDataEffect.tsx @@ -16,7 +16,7 @@ export const CompanyTableMockDataEffect = () => { const setDataTableData = useSetDataTableData(); useEffect(() => { - setDataTableData(mockedCompaniesData, [], []); + setDataTableData(mockedCompaniesData); setTableColumns(companiesAvailableFieldDefinitions); }, [setDataTableData, setTableColumns]); diff --git a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx index aa09677aa..ca6a89d02 100644 --- a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx +++ b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx @@ -1,15 +1,35 @@ +import styled from '@emotion/styled'; + 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 { useUpdateOneCompanyMutation } from '~/generated/graphql'; +import CompanyTableEffect from './CompanyTableEffect'; import { CompanyTableMockDataEffect } from './CompanyTableMockDataEffect'; +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; +`; + export const CompanyTableMockMode = () => { return ( - - + + + + + } + optionsDropdownScopeId={TableOptionsDropdownId} + /> - - + + + ); }; diff --git a/front/src/modules/metadata/components/ObjectDataTableEffect.tsx b/front/src/modules/metadata/components/ObjectDataTableEffect.tsx index 5e981d5f6..c29e33d8d 100644 --- a/front/src/modules/metadata/components/ObjectDataTableEffect.tsx +++ b/front/src/modules/metadata/components/ObjectDataTableEffect.tsx @@ -1,6 +1,14 @@ 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 { useMetadataObjectInContext } from '../hooks/useMetadataObjectInContext'; import { useSetObjectDataTableData } from '../hooks/useSetDataTableData'; import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier'; @@ -9,14 +17,33 @@ export type ObjectDataTableEffectProps = Pick< 'objectNamePlural' >; -// TODO: merge in a single effect component +// This should be migrated to DataTable at some point export const ObjectDataTableEffect = ({ objectNamePlural, }: ObjectDataTableEffectProps) => { const setDataTableData = useSetObjectDataTableData(); + const { foundMetadataObject } = useMetadataObjectInContext(); + + const tableFilters = useRecoilScopedValue( + tableFiltersScopedState, + TableRecoilScopeContext, + ); + + const tableSorts = useRecoilScopedValue( + tableSortsScopedState, + TableRecoilScopeContext, + ); const { objects, loading } = useFindManyObjects({ - objectNamePlural, + objectNamePlural: objectNamePlural, + filter: turnFiltersIntoWhereClauseV2( + tableFilters, + foundMetadataObject?.fields ?? [], + ), + orderBy: turnSortsIntoOrderByV2( + tableSorts, + foundMetadataObject?.fields ?? [], + ), }); useEffect(() => { diff --git a/front/src/modules/metadata/components/ObjectShowPage.tsx b/front/src/modules/metadata/components/ObjectShowPage.tsx index 6dcb9f28e..3f7de1e52 100644 --- a/front/src/modules/metadata/components/ObjectShowPage.tsx +++ b/front/src/modules/metadata/components/ObjectShowPage.tsx @@ -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 { InlineCellHotkeyScope } from '@/ui/data/inline-cell/types/InlineCellHotkeyScope'; import { IconBuildingSkyscraper } from '@/ui/display/icon'; +import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons'; import { PageBody } from '@/ui/layout/page/PageBody'; import { PageContainer } from '@/ui/layout/page/PageContainer'; import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton'; @@ -33,6 +34,8 @@ export const ObjectShowPage = () => { objectId: string; }>(); + const { icons } = useLazyLoadIcons(); + const { foundMetadataObject } = useFindOneMetadataObject({ objectNameSingular, }); @@ -130,6 +133,7 @@ export const ObjectShowPage = () => { field: metadataField, position: index, metadataObject: foundMetadataObject, + icons, }), useUpdateEntityMutation: useUpdateOneObjectMutation, hotkeyScope: InlineCellHotkeyScope.InlineCell, diff --git a/front/src/modules/metadata/components/ObjectTable.tsx b/front/src/modules/metadata/components/ObjectTable.tsx index cd2a19221..6fc9a8f17 100644 --- a/front/src/modules/metadata/components/ObjectTable.tsx +++ b/front/src/modules/metadata/components/ObjectTable.tsx @@ -1,11 +1,23 @@ import styled from '@emotion/styled'; +import { useRecoilCallback, useSetRecoilState } from 'recoil'; 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 { 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 { useMetadataObjectInContext } from '../hooks/useMetadataObjectInContext'; import { useUpdateOneObject } from '../hooks/useUpdateOneObject'; import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier'; @@ -28,9 +40,26 @@ export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => { const { updateOneObject } = useUpdateOneObject({ objectNamePlural, }); - + const { columnDefinitions, foundMetadataObject } = + useMetadataObjectInContext(); + const tableScopeId = foundMetadataObject?.namePlural ?? ''; 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 = ({ variables, }: { @@ -48,16 +77,32 @@ export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => { }; return ( - {}}> + { + setTableColumns( + viewFieldsToColumnDefinitions(viewFields, columnDefinitions), + ); + }} + onViewFiltersChange={(viewFilters) => { + setTableFilters(viewFiltersToFilters(viewFilters)); + }} + onViewSortsChange={(viewSorts) => { + setTableSorts(viewSortsToSorts(viewSorts)); + }} + > {}, + onColumnsChange: useRecoilCallback(() => (columns) => { + setCurrentViewFields?.(columnDefinitionsToViewFields(columns)); + persistViewFields(columnDefinitionsToViewFields(columns)); + }), }} > } - optionsDropdownScopeId="table-dropdown-option" + optionsDropdownScopeId={TableOptionsDropdownId} /> diff --git a/front/src/modules/metadata/components/ObjectTableEffect.tsx b/front/src/modules/metadata/components/ObjectTableEffect.tsx index d5e53d32b..df5fad1d4 100644 --- a/front/src/modules/metadata/components/ObjectTableEffect.tsx +++ b/front/src/modules/metadata/components/ObjectTableEffect.tsx @@ -16,69 +16,43 @@ export const ObjectTableEffect = () => { setViewObjectId, } = useView(); - // const [, setTableColumns] = useRecoilScopedState( - // tableColumnsScopedState, - // TableRecoilScopeContext, - // ); + const { + columnDefinitions, + filterDefinitions, + sortDefinitions, + foundMetadataObject, + } = useMetadataObjectInContext(); - // const [, setTableSorts] = useRecoilScopedState( - // tableSortsScopedState, - // TableRecoilScopeContext, - // ); - - // const [, setTableFilters] = useRecoilScopedState( - // tableFiltersScopedState, - // TableRecoilScopeContext, - // ); - - const { columnDefinitions, objectNamePlural } = useMetadataObjectInContext(); + const tableScopeId = foundMetadataObject?.namePlural ?? ''; const setAvailableTableColumns = useSetRecoilState( - availableTableColumnsScopedState(objectNamePlural ?? ''), + availableTableColumnsScopedState(tableScopeId), ); useEffect(() => { - setAvailableSortDefinitions?.([]); // TODO: extract from metadata fields - setAvailableFilterDefinitions?.([]); // TODO: extract from metadata fields - setAvailableFieldDefinitions?.(columnDefinitions); - setViewObjectId?.(objectNamePlural); + if (!foundMetadataObject) { + return; + } + setViewObjectId?.(foundMetadataObject.id); setViewType?.(ViewType.Table); + setAvailableSortDefinitions?.(sortDefinitions); + setAvailableFilterDefinitions?.(filterDefinitions); + setAvailableFieldDefinitions?.(columnDefinitions); + setAvailableTableColumns(columnDefinitions); }, [ setAvailableTableColumns, setViewObjectId, setViewType, columnDefinitions, - objectNamePlural, setAvailableSortDefinitions, setAvailableFilterDefinitions, 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 <>; }; diff --git a/front/src/modules/metadata/components/ObjectTablePage.tsx b/front/src/modules/metadata/components/ObjectTablePage.tsx index 0e375d2fd..74e57e938 100644 --- a/front/src/modules/metadata/components/ObjectTablePage.tsx +++ b/front/src/modules/metadata/components/ObjectTablePage.tsx @@ -49,9 +49,7 @@ export const ObjectTablePage = () => { }); const handleAddButtonClick = async () => { - createOneObject?.({ - name: 'Test', - }); + createOneObject?.({}); }; return ( diff --git a/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts b/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts index 6e1413bd7..824a87f94 100644 --- a/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts +++ b/front/src/modules/metadata/hooks/useFindOneMetadataObject.ts @@ -2,9 +2,14 @@ import { gql } from '@apollo/client'; import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition'; 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 { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition'; +import { formatMetadataFieldAsFilterDefinition } from '../utils/formatMetadataFieldAsFilterDefinition'; +import { formatMetadataFieldAsSortDefinition } from '../utils/formatMetadataFieldAsSortDefinition'; import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation'; import { generateDeleteOneObjectMutation } from '../utils/generateDeleteOneObjectMutation'; import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery'; @@ -25,6 +30,8 @@ export const useFindOneMetadataObject = ({ object.nameSingular === objectNameSingular, ); + const { icons } = useLazyLoadIcons(); + const objectNotFoundInMetadata = metadataObjects.length === 0 || (metadataObjects.length > 0 && !foundMetadataObject); @@ -35,6 +42,23 @@ export const useFindOneMetadataObject = ({ position: index, field, 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, objectNotFoundInMetadata, columnDefinitions, + filterDefinitions, + sortDefinitions, findManyQuery, findOneQuery, createOneMutation, diff --git a/front/src/modules/metadata/hooks/useMetadataObjectInContext.ts b/front/src/modules/metadata/hooks/useMetadataObjectInContext.ts index 83967ae8c..2456aa62a 100644 --- a/front/src/modules/metadata/hooks/useMetadataObjectInContext.ts +++ b/front/src/modules/metadata/hooks/useMetadataObjectInContext.ts @@ -13,15 +13,22 @@ export const useMetadataObjectInContext = () => { ); } - const { foundMetadataObject, loading, columnDefinitions } = - useFindOneMetadataObject({ - objectNamePlural: context.objectNamePlural, - }); + const { + foundMetadataObject, + loading, + columnDefinitions, + filterDefinitions, + sortDefinitions, + } = useFindOneMetadataObject({ + objectNamePlural: context.objectNamePlural, + }); return { ...context, foundMetadataObject, loading, columnDefinitions, + filterDefinitions, + sortDefinitions, }; }; diff --git a/front/src/modules/metadata/hooks/useSetDataTableData.ts b/front/src/modules/metadata/hooks/useSetDataTableData.ts index df0095e45..2b6250474 100644 --- a/front/src/modules/metadata/hooks/useSetDataTableData.ts +++ b/front/src/modules/metadata/hooks/useSetDataTableData.ts @@ -3,16 +3,13 @@ import { useRecoilCallback } from 'recoil'; import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection'; import { isFetchingDataTableDataState } from '@/ui/data/data-table/states/isFetchingDataTableDataState'; 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 { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFamilyState'; -import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId'; -import { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState'; +import { useView } from '@/views/hooks/useView'; export const useSetObjectDataTableData = () => { const resetTableRowSelection = useResetTableRowSelection(); - - const tableContextScopeId = useRecoilScopeId(TableRecoilScopeContext); + const { setEntityCountInCurrentView } = useView(); return useRecoilCallback( ({ set, snapshot }) => @@ -40,14 +37,10 @@ export const useSetObjectDataTableData = () => { resetTableRowSelection(); set(numberOfTableRowsState, entityIds.length); - - set( - availableSortDefinitionsScopedState({ scopeId: tableContextScopeId }), - [], - ); + setEntityCountInCurrentView(entityIds.length); set(isFetchingDataTableDataState, false); }, - [resetTableRowSelection, tableContextScopeId], + [resetTableRowSelection, setEntityCountInCurrentView], ); }; diff --git a/front/src/modules/metadata/utils/formatMetadataFieldAsColumnDefinition.ts b/front/src/modules/metadata/utils/formatMetadataFieldAsColumnDefinition.ts index d9b139502..27bdab800 100644 --- a/front/src/modules/metadata/utils/formatMetadataFieldAsColumnDefinition.ts +++ b/front/src/modules/metadata/utils/formatMetadataFieldAsColumnDefinition.ts @@ -1,30 +1,20 @@ import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition'; 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'; -const parseFieldType = (fieldType: string): FieldType => { - if (fieldType === 'url') { - return 'urlV2'; - } - - if (fieldType === 'money') { - return 'moneyAmountV2'; - } - - return fieldType as FieldType; -}; +import { parseFieldType } from './parseFieldType'; export const formatMetadataFieldAsColumnDefinition = ({ position, field, metadataObject, + icons, }: { position: number; field: MetadataObject['fields'][0]; metadataObject: Omit; + icons: Record; }): ColumnDefinition => ({ position, fieldId: field.id, @@ -35,7 +25,7 @@ export const formatMetadataFieldAsColumnDefinition = ({ fieldName: field.name, placeHolder: field.label, }, - Icon: IconBrandLinkedin, + Icon: icons[field.icon ?? 'Icon123'], isVisible: true, basePathToShowPage: `/object/${metadataObject.nameSingular}/`, }); diff --git a/front/src/modules/metadata/utils/formatMetadataFieldAsFilterDefinition.ts b/front/src/modules/metadata/utils/formatMetadataFieldAsFilterDefinition.ts new file mode 100644 index 000000000..9872c8eec --- /dev/null +++ b/front/src/modules/metadata/utils/formatMetadataFieldAsFilterDefinition.ts @@ -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; +}): FilterDefinition => ({ + fieldId: field.id, + label: field.label, + Icon: icons[field.icon ?? 'Icon123'], + type: 'text', +}); diff --git a/front/src/modules/metadata/utils/formatMetadataFieldAsSortDefinition.ts b/front/src/modules/metadata/utils/formatMetadataFieldAsSortDefinition.ts new file mode 100644 index 000000000..3ad900a6e --- /dev/null +++ b/front/src/modules/metadata/utils/formatMetadataFieldAsSortDefinition.ts @@ -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; +}): SortDefinition => ({ + fieldId: field.id, + label: field.label, + Icon: icons[field.icon ?? 'Icon123'], +}); diff --git a/front/src/modules/metadata/utils/parseFieldType.ts b/front/src/modules/metadata/utils/parseFieldType.ts new file mode 100644 index 000000000..9ccb9cdd9 --- /dev/null +++ b/front/src/modules/metadata/utils/parseFieldType.ts @@ -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; +}; diff --git a/front/src/modules/people/hooks/useCreateActivityForPeople.ts b/front/src/modules/people/hooks/useCreateActivityForPeople.ts index 5964d7a68..02c5261f9 100644 --- a/front/src/modules/people/hooks/useCreateActivityForPeople.ts +++ b/front/src/modules/people/hooks/useCreateActivityForPeople.ts @@ -1,4 +1,4 @@ -import { useRecoilCallback, useRecoilValue } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds'; import { @@ -12,12 +12,14 @@ import { ActivityType, Person } from '~/generated/graphql'; export const useCreateActivityForPeople = () => { const openCreateActivityRightDrawer = useOpenCreateActivityDrawerForSelectedRowIds(); - const selectedRowIds = useRecoilValue(selectedRowIdsSelector); return useRecoilCallback( ({ snapshot }) => (type: ActivityType) => { const relatedEntites: ActivityTargetableEntity[] = []; + const selectedRowIds = Object.keys( + snapshot.getLoadable(selectedRowIdsSelector).getValue(), + ); for (const id of selectedRowIds) { const person = snapshot .getLoadable(entityFieldsFamilyState(id)) @@ -39,6 +41,6 @@ export const useCreateActivityForPeople = () => { relatedEntites, ); }, - [selectedRowIds, openCreateActivityRightDrawer], + [openCreateActivityRightDrawer], ); }; diff --git a/front/src/modules/people/hooks/usePersonTableActionBarEntries.tsx b/front/src/modules/people/hooks/usePersonTableActionBarEntries.tsx deleted file mode 100644 index 2705a25c4..000000000 --- a/front/src/modules/people/hooks/usePersonTableActionBarEntries.tsx +++ /dev/null @@ -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(), - }, - ]), - }; -}; diff --git a/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx b/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx index a61811f22..654e94c30 100644 --- a/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx +++ b/front/src/modules/people/hooks/usePersonTableContextMenuEntries.tsx @@ -1,5 +1,5 @@ import { getOperationName } from '@apollo/client/utilities'; -import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useResetTableRowSelection } from '@/ui/data/data-table/hooks/useResetTableRowSelection'; @@ -12,6 +12,7 @@ import { IconNotes, IconTrash, } from '@/ui/display/icon'; +import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState'; import { ActivityType, @@ -25,38 +26,41 @@ import { useCreateActivityForPeople } from './useCreateActivityForPeople'; export const usePersonTableContextMenuEntries = () => { const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState); - + const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState); const createActivityForPeople = useCreateActivityForPeople(); - const selectedRowIds = useRecoilValue(selectedRowIdsSelector); - const [tableRowIds, setTableRowIds] = useRecoilState(tableRowIdsState); - + const setTableRowIds = useSetRecoilState(tableRowIdsState); const resetRowSelection = useResetTableRowSelection(); - const selectedPersonId = selectedRowIds.length === 1 ? selectedRowIds[0] : ''; - const { data } = useGetFavoritesQuery(); - const favorites = data?.findFavorites; - - const isFavorite = - !!selectedPersonId && - !!favorites?.find((favorite) => favorite.person?.id === selectedPersonId); - 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(); if (isFavorite) deletePersonFavorite(selectedPersonId); else insertPersonFavorite(selectedPersonId); - }; + }); const [deleteManyPerson] = useDeleteManyPersonMutation({ refetchQueries: [getOperationName(GET_PEOPLE) ?? ''], }); - const handleDeleteClick = async () => { - const rowIdsToDelete = selectedRowIds; + const handleDeleteClick = useRecoilCallback(({ snapshot }) => async () => { + const rowIdsToDelete = snapshot + .getLoadable(selectedRowIdsSelector) + .getValue(); resetRowSelection(); @@ -71,15 +75,28 @@ export const usePersonTableContextMenuEntries = () => { }, }, update: () => { - setTableRowIds( + setTableRowIds((tableRowIds) => tableRowIds.filter((id) => !rowIdsToDelete.includes(id)), ); }, }); - }; + }); 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([ { label: 'New task', @@ -108,6 +125,27 @@ export const usePersonTableContextMenuEntries = () => { accent: 'danger', 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(), + }, + ]); + }), }; }; diff --git a/front/src/modules/people/table/components/PersonTable.tsx b/front/src/modules/people/table/components/PersonTable.tsx index b6fbaeb33..da832ab84 100644 --- a/front/src/modules/people/table/components/PersonTable.tsx +++ b/front/src/modules/people/table/components/PersonTable.tsx @@ -3,7 +3,6 @@ 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'; @@ -53,8 +52,8 @@ export const PersonTable = () => { viewScopeId, }); - const { setContextMenuEntries } = usePersonTableContextMenuEntries(); - const { setActionBarEntries } = usePersonTableActionBarEntries(); + const { setContextMenuEntries, setActionBarEntries } = + usePersonTableContextMenuEntries(); const updatePerson = async (variables: UpdateOnePersonMutationVariables) => { updateEntityMutation({ @@ -74,7 +73,6 @@ export const PersonTable = () => { const StyledContainer = styled.div` display: flex; flex-direction: column; - height: 100%; overflow: auto; `; diff --git a/front/src/modules/people/table/components/PersonTableEffect.tsx b/front/src/modules/people/table/components/PersonTableEffect.tsx index 6f6f7bf01..b7a87f6ff 100644 --- a/front/src/modules/people/table/components/PersonTableEffect.tsx +++ b/front/src/modules/people/table/components/PersonTableEffect.tsx @@ -46,29 +46,6 @@ const PeopleTableEffect = () => { setViewObjectId, 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 <>; }; diff --git a/front/src/modules/ui/data/data-table/components/DataTable.tsx b/front/src/modules/ui/data/data-table/components/DataTable.tsx index 94ec5a9d8..cfa549624 100644 --- a/front/src/modules/ui/data/data-table/components/DataTable.tsx +++ b/front/src/modules/ui/data/data-table/components/DataTable.tsx @@ -79,6 +79,7 @@ const StyledTableContainer = styled.div` flex-direction: column; height: 100%; overflow: auto; + position: relative; `; type DataTableProps = { @@ -121,20 +122,20 @@ export const DataTable = ({ updateEntityMutation }: DataTableProps) => { return ( - + -
+
+
- diff --git a/front/src/modules/ui/data/data-table/components/DataTableEffect.tsx b/front/src/modules/ui/data/data-table/components/DataTableEffect.tsx index 54a8cd639..c2a996af3 100644 --- a/front/src/modules/ui/data/data-table/components/DataTableEffect.tsx +++ b/front/src/modules/ui/data/data-table/components/DataTableEffect.tsx @@ -23,10 +23,8 @@ export const DataTableEffect = ({ getRequestResultKey, getRequestOptimisticEffectDefinition, - filterDefinitionArray, setActionBarEntries, setContextMenuEntries, - sortDefinitionArray, }: { useGetRequest: typeof useGetCompaniesQuery | typeof useGetPeopleQuery; getRequestResultKey: string; @@ -59,7 +57,7 @@ export const DataTableEffect = ({ onCompleted: (data: any) => { const entities = data[getRequestResultKey] ?? []; - setDataTableData(entities, filterDefinitionArray, sortDefinitionArray); + setDataTableData(entities); registerOptimisticEffect({ variables: { orderBy: sortsOrderBy, where: tablefiltersWhere }, diff --git a/front/src/modules/ui/data/data-table/hooks/useSetDataTableData.ts b/front/src/modules/ui/data/data-table/hooks/useSetDataTableData.ts index 9e77272a5..e44858044 100644 --- a/front/src/modules/ui/data/data-table/hooks/useSetDataTableData.ts +++ b/front/src/modules/ui/data/data-table/hooks/useSetDataTableData.ts @@ -1,15 +1,10 @@ import { useRecoilCallback } from 'recoil'; 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 { availableSortDefinitionsScopedState } from '@/views/states/availableSortDefinitionsScopedState'; -import { SortDefinition } from '../../sort/types/SortDefinition'; import { isFetchingDataTableDataState } from '../states/isFetchingDataTableDataState'; import { numberOfTableRowsState } from '../states/numberOfTableRowsState'; -import { TableRecoilScopeContext } from '../states/recoil-scope-contexts/TableRecoilScopeContext'; import { tableRowIdsState } from '../states/tableRowIdsState'; import { useResetTableRowSelection } from './useResetTableRowSelection'; @@ -18,15 +13,9 @@ export const useSetDataTableData = () => { const resetTableRowSelection = useResetTableRowSelection(); const { setEntityCountInCurrentView } = useView(); - const tableContextScopeId = useRecoilScopeId(TableRecoilScopeContext); - return useRecoilCallback( ({ set, snapshot }) => - ( - newEntityArray: T[], - filterDefinitionArray: FilterDefinition[], - sortDefinitionArray: SortDefinition[], - ) => { + (newEntityArray: T[]) => { for (const entity of newEntityArray) { const currentEntity = snapshot .getLoadable(entityFieldsFamilyState(entity.id)) @@ -50,16 +39,9 @@ export const useSetDataTableData = () => { resetTableRowSelection(); set(numberOfTableRowsState, entityIds.length); - setEntityCountInCurrentView(entityIds.length); - - set( - availableSortDefinitionsScopedState({ scopeId: tableContextScopeId }), - sortDefinitionArray, - ); - set(isFetchingDataTableDataState, false); }, - [resetTableRowSelection, setEntityCountInCurrentView, tableContextScopeId], + [resetTableRowSelection, setEntityCountInCurrentView], ); }; diff --git a/front/src/modules/ui/data/filter/utils/turnFiltersIntoWhereClauseV2.ts b/front/src/modules/ui/data/filter/utils/turnFiltersIntoWhereClauseV2.ts new file mode 100644 index 000000000..7270022f6 --- /dev/null +++ b/front/src/modules/ui/data/filter/utils/turnFiltersIntoWhereClauseV2.ts @@ -0,0 +1,53 @@ +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { Field } from '~/generated/graphql'; + +import { Filter } from '../types/Filter'; + +type FilterToTurnIntoWhereClause = Omit & { + definition: { + type: Filter['definition']['type']; + }; +}; + +export const turnFiltersIntoWhereClauseV2 = ( + filters: FilterToTurnIntoWhereClause[], + fields: Pick[], +) => { + const whereClause: Record = {}; + + 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; +}; diff --git a/front/src/modules/ui/data/sort/hooks/useSortStates.ts b/front/src/modules/ui/data/sort/hooks/useSortStates.ts index d4662d661..4e4c79d74 100644 --- a/front/src/modules/ui/data/sort/hooks/useSortStates.ts +++ b/front/src/modules/ui/data/sort/hooks/useSortStates.ts @@ -1,6 +1,6 @@ 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'; export const useSortStates = (scopeId: string) => { diff --git a/front/src/modules/ui/data/sort/states/availableSortsScopedState.ts b/front/src/modules/ui/data/sort/states/availableSortDefinitionsScopedState.ts similarity index 55% rename from front/src/modules/ui/data/sort/states/availableSortsScopedState.ts rename to front/src/modules/ui/data/sort/states/availableSortDefinitionsScopedState.ts index 1e8354cdb..0258a0d85 100644 --- a/front/src/modules/ui/data/sort/states/availableSortsScopedState.ts +++ b/front/src/modules/ui/data/sort/states/availableSortDefinitionsScopedState.ts @@ -2,7 +2,9 @@ import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScope import { SortDefinition } from '../types/SortDefinition'; -export const availableSortsScopedState = createScopedState({ - key: 'availableSortsScopedState', +export const availableSortDefinitionsScopedState = createScopedState< + SortDefinition[] +>({ + key: 'availableSortDefinitionsScopedState', defaultValue: [], }); diff --git a/front/src/modules/ui/data/sort/utils/turnSortsIntoOrderByV2.ts b/front/src/modules/ui/data/sort/utils/turnSortsIntoOrderByV2.ts new file mode 100644 index 000000000..306a030a2 --- /dev/null +++ b/front/src/modules/ui/data/sort/utils/turnSortsIntoOrderByV2.ts @@ -0,0 +1,26 @@ +import { Field } from '~/generated/graphql'; + +import { Sort } from '../types/Sort'; + +export const turnSortsIntoOrderByV2 = ( + sorts: Sort[], + fields: Pick[], +) => { + const sortsObject: Record = {}; + 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; +}; diff --git a/front/src/modules/ui/layout/board/components/EntityBoard.tsx b/front/src/modules/ui/layout/board/components/EntityBoard.tsx index 8880896c2..4022a69a7 100644 --- a/front/src/modules/ui/layout/board/components/EntityBoard.tsx +++ b/front/src/modules/ui/layout/board/components/EntityBoard.tsx @@ -42,6 +42,7 @@ const StyledWrapper = styled.div` flex-direction: column; height: 100%; overflow: hidden; + position: relative; width: 100%; `; diff --git a/front/src/modules/ui/navigation/action-bar/components/__stories__/ActionBar.stories.tsx b/front/src/modules/ui/navigation/action-bar/components/__stories__/ActionBar.stories.tsx index 43fed4d32..28a5070d8 100644 --- a/front/src/modules/ui/navigation/action-bar/components/__stories__/ActionBar.stories.tsx +++ b/front/src/modules/ui/navigation/action-bar/components/__stories__/ActionBar.stories.tsx @@ -2,7 +2,7 @@ import { MemoryRouter } from 'react-router-dom'; import { Meta, StoryObj } from '@storybook/react'; import { useSetRecoilState } from 'recoil'; -import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries'; +import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyTableContextMenuEntries'; import { CompanyTableMockMode } from '@/companies/table/components/CompanyTableMockMode'; import { TableRecoilScopeContext } from '@/ui/data/data-table/states/recoil-scope-contexts/TableRecoilScopeContext'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; @@ -12,7 +12,7 @@ import { actionBarOpenState } from '../../states/actionBarIsOpenState'; import { ActionBar } from '../ActionBar'; const FilledActionBar = (props: { selectedIds: string[] }) => { - const { setActionBarEntries } = useCompanyTableActionBarEntries(); + const { setActionBarEntries } = useCompanyTableContextMenuEntries(); setActionBarEntries(); const setActionBarOpenState = useSetRecoilState(actionBarOpenState); setActionBarOpenState(true); diff --git a/front/src/pages/settings/data-model/SettingsNewObject.tsx b/front/src/pages/settings/data-model/SettingsNewObject.tsx index 0cb777356..ce4df2da5 100644 --- a/front/src/pages/settings/data-model/SettingsNewObject.tsx +++ b/front/src/pages/settings/data-model/SettingsNewObject.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useCreateOneObject } from '@/metadata/hooks/useCreateOneObject'; import { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; 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 { Section } from '@/ui/layout/section/components/Section'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; +import { ViewType } from '@/views/types/ViewType'; export const SettingsNewObject = () => { const navigate = useNavigate(); @@ -29,6 +31,10 @@ export const SettingsNewObject = () => { disabledMetadataObjects: disabledObjects, } = useMetadataObjectForSettings(); + const { createOneObject: createOneView } = useCreateOneObject({ + objectNamePlural: 'viewsV2', + }); + const [selectedStandardObjectIds, setSelectedStandardObjectIds] = useState< Record >({}); @@ -60,12 +66,18 @@ export const SettingsNewObject = () => { } if (selectedObjectType === 'Custom') { - await createObject({ + const createdObject = await createObject({ labelPlural: customFormValues.labelPlural, labelSingular: customFormValues.labelSingular, description: customFormValues.description, icon: customFormValues.icon, }); + + await createOneView?.({ + objectId: createdObject.data?.createOneObject.id, + type: ViewType.Table, + name: `All ${customFormValues.labelPlural}`, + }); } navigate('/settings/objects'); diff --git a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx index 42edc7b22..f2da9f63d 100644 --- a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx +++ b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx @@ -1,8 +1,11 @@ import { useEffect, useState } from 'react'; 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 { useMetadataObjectForSettings } from '@/metadata/hooks/useMetadataObjectForSettings'; +import { PaginatedObjectTypeResults } from '@/metadata/types/PaginatedObjectTypeResults'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; @@ -13,6 +16,8 @@ import { AppPath } from '@/types/AppPath'; import { IconSettings } from '@/ui/display/icon'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; +import { View } from '@/views/types/View'; +import { ViewType } from '@/views/types/ViewType'; export const SettingsObjectNewFieldStep2 = () => { const navigate = useNavigate(); @@ -36,15 +41,47 @@ export const SettingsObjectNewFieldStep2 = () => { type: MetadataFieldDataType; }>({ icon: 'IconUsers', label: '', type: 'number' }); - if (!activeMetadataObject) return null; + const [objectViews, setObjectViews] = useState([]); + + const { createOneObject: createOneViewField } = useCreateOneObject({ + objectNamePlural: 'viewFieldsV2', + }); + + useFindManyObjects({ + objectNamePlural: 'viewsV2', + filter: { + type: { eq: ViewType.Table }, + objectId: { eq: activeMetadataObject?.id }, + }, + onCompleted: async (data: PaginatedObjectTypeResults) => { + const views = data.edges; + + if (!views) { + return; + } + + setObjectViews(data.edges.map(({ node }) => node)); + }, + }); + + if (!activeMetadataObject || !objectViews.length) return null; const canSave = !!formValues.label; const handleSave = async () => { - await createMetadataField({ + const createdField = await createMetadataField({ ...formValues, 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}`); }; diff --git a/server/src/metadata/commands/data-seed-tenant.command.ts b/server/src/metadata/commands/data-seed-tenant.command.ts index 75e99a1e2..aa14737c8 100644 --- a/server/src/metadata/commands/data-seed-tenant.command.ts +++ b/server/src/metadata/commands/data-seed-tenant.command.ts @@ -1,7 +1,7 @@ import { Command, CommandRunner, Option } from 'nest-commander'; -import { TenantInitialisationService } from '../tenant-initialisation/tenant-initialisation.service'; -import { DataSourceMetadataService } from '../data-source-metadata/data-source-metadata.service'; +import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service'; +import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service'; // TODO: implement dry-run interface DataSeedTenantOptions { diff --git a/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts b/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts index 0bc4e7dba..1d4402578 100644 --- a/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts +++ b/server/src/metadata/field-metadata/hooks/before-create-one-field.hook.ts @@ -22,7 +22,7 @@ export class BeforeCreateOneField } instance.input.workspaceId = workspaceId; - instance.input.isActive = false; + instance.input.isActive = true; instance.input.isCustom = true; return instance; } diff --git a/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts b/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts index d3b2d79bb..79f1677ff 100644 --- a/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts +++ b/server/src/metadata/object-metadata/hooks/before-create-one-object.hook.ts @@ -32,7 +32,7 @@ export class BeforeCreateOneObject instance.input.dataSourceId = lastDataSourceMetadata.id; instance.input.targetTableName = instance.input.namePlural; instance.input.workspaceId = workspaceId; - instance.input.isActive = false; + instance.input.isActive = true; instance.input.isCustom = true; return instance; }