diff --git a/.vscode/settings.json b/.vscode/settings.json
index a0240964a5999..bb8e0fdb00336 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,18 +5,21 @@
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
+ "source.addMissingImports": "always"
}
},
"[javascript]": {
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
+ "source.addMissingImports": "always"
}
},
"[typescriptreact]": {
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
+ "source.addMissingImports": "always"
}
},
"[json]": {
diff --git a/front/src/App.tsx b/front/src/App.tsx
index afa91e55d5cc2..9819a65ec34cc 100644
--- a/front/src/App.tsx
+++ b/front/src/App.tsx
@@ -28,6 +28,8 @@ import { SettingsWorkspaceMembers } from '~/pages/settings/SettingsWorkspaceMemb
import { Tasks } from '~/pages/tasks/Tasks';
import { getPageTitleFromPath } from '~/utils/title-utils';
+import { ObjectTablePage } from './pages/companies/ObjectsTable';
+
export const App = () => {
const { pathname } = useLocation();
const pageTitle = getPageTitleFromPath(pathname);
@@ -54,6 +56,16 @@ export const App = () => {
} />
} />
+
+ }
+ />
+
,
];
+
+export const suppliersAvailableColumnDefinitions: ColumnDefinition[] =
+ [
+ {
+ key: 'name',
+ name: 'Name',
+ Icon: IconBuildingSkyscraper,
+ size: 180,
+ index: 0,
+ type: 'text',
+ metadata: {
+ fieldName: 'name',
+ placeHolder: 'Company Name',
+ },
+ isVisible: true,
+ buttonIcon: IconArrowUpRight,
+ infoTooltipContent: 'The company name.',
+ basePathToShowPage: '/companies/',
+ } satisfies ColumnDefinition,
+ {
+ key: 'city',
+ name: 'City',
+ Icon: IconBuildingSkyscraper,
+ size: 180,
+ index: 0,
+ type: 'text',
+ metadata: {
+ fieldName: 'city',
+ placeHolder: 'Company Name',
+ },
+ isVisible: true,
+ buttonIcon: IconArrowUpRight,
+ infoTooltipContent: 'The company name.',
+ basePathToShowPage: '/companies/',
+ } satisfies ColumnDefinition,
+ ];
diff --git a/front/src/modules/metadata/components/FetchMetadataEffect.tsx b/front/src/modules/metadata/components/FetchMetadataEffect.tsx
index 698c82168ca5f..d4ff6b6427d7a 100644
--- a/front/src/modules/metadata/components/FetchMetadataEffect.tsx
+++ b/front/src/modules/metadata/components/FetchMetadataEffect.tsx
@@ -21,7 +21,10 @@ export const FetchMetadataEffect = () => {
query: GET_ALL_OBJECTS,
});
- if (objects.data.objects.edges.length > 0) {
+ if (
+ objects.data.objects.edges.length > 0 &&
+ metadataObjects.length === 0
+ ) {
const formattedObjects: MetadataObject[] =
objects.data.objects.edges.map((object) => ({
...object.node,
diff --git a/front/src/modules/metadata/components/ObjectDataTableEffect.tsx b/front/src/modules/metadata/components/ObjectDataTableEffect.tsx
new file mode 100644
index 0000000000000..3badecf633f1f
--- /dev/null
+++ b/front/src/modules/metadata/components/ObjectDataTableEffect.tsx
@@ -0,0 +1,68 @@
+import { useEffect } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { useRecoilCallback } from 'recoil';
+
+import { TableRecoilScopeContext } from '@/ui/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
+import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
+import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
+import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
+import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState';
+import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState';
+import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState';
+
+import { useFindManyCustomObjects } from '../hooks/useFindManyCustomObjects';
+
+import { useSetObjectDataTableData } from './useSetDataTableData';
+
+export const ObjectDataTableEffect = ({
+ objectName,
+ objectNameSingular,
+}: {
+ objectNameSingular: string;
+ objectName: string;
+}) => {
+ const setDataTableData = useSetObjectDataTableData();
+
+ const { data } = useFindManyCustomObjects({ objectName });
+
+ useEffect(() => {
+ const entities = data?.['findMany' + objectNameSingular]?.edges ?? [];
+
+ setDataTableData(entities);
+ }, [data, objectNameSingular, setDataTableData]);
+
+ const [searchParams] = useSearchParams();
+ const tableRecoilScopeId = useRecoilScopeId(TableRecoilScopeContext);
+ const handleViewSelect = useRecoilCallback(
+ ({ set, snapshot }) =>
+ async (viewId: string) => {
+ const currentView = await snapshot.getPromise(
+ currentViewIdScopedState(tableRecoilScopeId),
+ );
+ if (currentView === viewId) {
+ return;
+ }
+
+ const savedFilters = await snapshot.getPromise(
+ savedFiltersFamilyState(viewId),
+ );
+ const savedSorts = await snapshot.getPromise(
+ savedSortsFamilyState(viewId),
+ );
+
+ set(filtersScopedState(tableRecoilScopeId), savedFilters);
+ set(sortsScopedState(tableRecoilScopeId), savedSorts);
+ set(currentViewIdScopedState(tableRecoilScopeId), viewId);
+ },
+ [tableRecoilScopeId],
+ );
+
+ useEffect(() => {
+ const viewId = searchParams.get('view');
+ if (viewId) {
+ handleViewSelect(viewId);
+ }
+ }, [handleViewSelect, searchParams]);
+
+ return <>>;
+};
diff --git a/front/src/modules/metadata/components/ObjectTable.tsx b/front/src/modules/metadata/components/ObjectTable.tsx
new file mode 100644
index 0000000000000..04bd98a342393
--- /dev/null
+++ b/front/src/modules/metadata/components/ObjectTable.tsx
@@ -0,0 +1,57 @@
+import { suppliersAvailableColumnDefinitions } from '@/companies/constants/companiesAvailableColumnDefinitions';
+import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
+import { DataTable } from '@/ui/data-table/components/DataTable';
+import { TableContext } from '@/ui/data-table/contexts/TableContext';
+import { TableRecoilScopeContext } from '@/ui/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
+import { ViewBarContext } from '@/ui/view-bar/contexts/ViewBarContext';
+import { useTableViews } from '@/views/hooks/useTableViews';
+
+import { ObjectDataTableEffect } from './ObjectDataTableEffect';
+
+export const ObjectTable = ({
+ objectName,
+ objectNameSingular,
+}: {
+ objectNameSingular: string;
+ objectName: string;
+}) => {
+ const { createView, deleteView, submitCurrentView, updateView } =
+ useTableViews({
+ objectId: 'company',
+ columnDefinitions: suppliersAvailableColumnDefinitions,
+ });
+
+ const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
+
+ return (
+ {
+ //
+ },
+ }}
+ >
+
+
+ {
+ //
+ }}
+ />
+
+
+ );
+};
diff --git a/front/src/modules/metadata/components/useSetDataTableData.ts b/front/src/modules/metadata/components/useSetDataTableData.ts
new file mode 100644
index 0000000000000..17025876bb5b4
--- /dev/null
+++ b/front/src/modules/metadata/components/useSetDataTableData.ts
@@ -0,0 +1,61 @@
+import { useRecoilCallback } from 'recoil';
+
+import { useResetTableRowSelection } from '@/ui/data-table/hooks/useResetTableRowSelection';
+import { isFetchingDataTableDataState } from '@/ui/data-table/states/isFetchingDataTableDataState';
+import { numberOfTableRowsState } from '@/ui/data-table/states/numberOfTableRowsState';
+import { TableRecoilScopeContext } from '@/ui/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
+import { tableRowIdsState } from '@/ui/data-table/states/tableRowIdsState';
+import { entityFieldsFamilyState } from '@/ui/field/states/entityFieldsFamilyState';
+import { useRecoilScopeId } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopeId';
+import { availableFiltersScopedState } from '@/ui/view-bar/states/availableFiltersScopedState';
+import { availableSortsScopedState } from '@/ui/view-bar/states/availableSortsScopedState';
+import { entityCountInCurrentViewState } from '@/ui/view-bar/states/entityCountInCurrentViewState';
+
+export const useSetObjectDataTableData = () => {
+ const resetTableRowSelection = useResetTableRowSelection();
+
+ const tableContextScopeId = useRecoilScopeId(TableRecoilScopeContext);
+
+ return useRecoilCallback(
+ ({ set, snapshot }) =>
+ (newEntityArrayRaw: T[]) => {
+ const newEntityArray = newEntityArrayRaw.map((entity) => entity.node);
+
+ for (const entity of newEntityArray) {
+ const currentEntity = snapshot
+ .getLoadable(entityFieldsFamilyState(entity.id))
+ .valueOrThrow();
+
+ if (JSON.stringify(currentEntity) !== JSON.stringify(entity)) {
+ set(entityFieldsFamilyState(entity.id), entity);
+ }
+ }
+
+ const entityIds = newEntityArray.map((entity) => entity.id);
+
+ // eslint-disable-next-line no-console
+ console.log({ newEntityArray, entityIds });
+
+ set(tableRowIdsState, (currentRowIds) => {
+ if (JSON.stringify(currentRowIds) !== JSON.stringify(entityIds)) {
+ return entityIds;
+ }
+
+ return currentRowIds;
+ });
+
+ resetTableRowSelection();
+
+ set(numberOfTableRowsState, entityIds.length);
+
+ set(entityCountInCurrentViewState, entityIds.length);
+
+ set(availableFiltersScopedState(tableContextScopeId), []);
+
+ set(availableSortsScopedState(tableContextScopeId), []);
+
+ set(isFetchingDataTableDataState, false);
+ },
+ [resetTableRowSelection, tableContextScopeId],
+ );
+};
diff --git a/front/src/modules/metadata/hooks/useCreateOneCustomObject.ts b/front/src/modules/metadata/hooks/useCreateOneCustomObject.ts
new file mode 100644
index 0000000000000..8d6517734eda5
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useCreateOneCustomObject.ts
@@ -0,0 +1,8 @@
+// TODO: add zod to validate that we have at least id on each object
+export const useCreateOneCustomObject = ({
+ _objectName,
+}: {
+ _objectName: string;
+}) => {
+ // TODO : code
+};
diff --git a/front/src/modules/metadata/hooks/useFindManyCustomObjects.ts b/front/src/modules/metadata/hooks/useFindManyCustomObjects.ts
new file mode 100644
index 0000000000000..497a434d9695e
--- /dev/null
+++ b/front/src/modules/metadata/hooks/useFindManyCustomObjects.ts
@@ -0,0 +1,59 @@
+import { gql, useQuery } from '@apollo/client';
+import { useRecoilState } from 'recoil';
+
+import { metadataObjectsState } from '../states/metadataObjectsState';
+import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery';
+
+// TODO: add zod to validate that we have at least id on each object
+export const useFindManyCustomObjects = ({
+ objectName,
+}: {
+ objectName: string;
+}) => {
+ const [metadataObjects] = useRecoilState(metadataObjectsState);
+
+ const foundObject = metadataObjects.find(
+ (object) => object.nameSingular === objectName,
+ );
+
+ // eslint-disable-next-line no-console
+ console.log({ foundObject });
+
+ const generatedQuery = foundObject
+ ? generateFindManyCustomObjectsQuery({
+ metadataObject: foundObject,
+ })
+ : gql`
+ query EmptyQuery {
+ empty
+ }
+ `;
+
+ const {
+ fetchMore: fetchMoreBase,
+ data,
+ loading,
+ error,
+ } = useQuery(generatedQuery, {
+ skip: !foundObject,
+ });
+
+ // eslint-disable-next-line no-console
+ console.log({ data, loading, error });
+
+ const fetchMore = ({ fromCursor }: { fromCursor: string }) => {
+ fetchMoreBase({
+ variables: { fromCursor },
+ });
+ };
+
+ const objectNotFoundInMetadata = metadataObjects.length > 0 && !foundObject;
+
+ return {
+ data,
+ loading,
+ error,
+ fetchMore,
+ objectNotFoundInMetadata,
+ };
+};
diff --git a/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts b/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts
new file mode 100644
index 0000000000000..b1308f127551d
--- /dev/null
+++ b/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts
@@ -0,0 +1,27 @@
+import { gql } from '@apollo/client';
+
+import { MetadataObject } from '../types/MetadataObject';
+
+export const generateFindManyCustomObjectsQuery = ({
+ metadataObject,
+ _fromCursor,
+}: {
+ metadataObject: MetadataObject;
+ _fromCursor?: string;
+}) => {
+ return gql`
+ query CustomQuery${metadataObject.nameSingular} {
+ findMany${metadataObject.nameSingular}{
+ edges {
+ node {
+ id
+ ${metadataObject.fields
+ .map((field) => field.nameSingular)
+ .join('\n')}
+ }
+ cursor
+ }
+ }
+ }
+ `;
+};
diff --git a/front/src/modules/types/AppPath.ts b/front/src/modules/types/AppPath.ts
index f881040ec3e57..51e7af629d766 100644
--- a/front/src/modules/types/AppPath.ts
+++ b/front/src/modules/types/AppPath.ts
@@ -17,6 +17,8 @@ export enum AppPath {
PersonShowPage = '/person/:personId',
TasksPage = '/tasks',
OpportunitiesPage = '/opportunities',
+ ObjectTablePage = '/:objectName',
+
SettingsCatchAll = `/settings/*`,
// Impersonate
diff --git a/front/src/modules/ui/data-table/components/DataTableBody.tsx b/front/src/modules/ui/data-table/components/DataTableBody.tsx
index 62af6f4eeba2d..2467762cbe7b1 100644
--- a/front/src/modules/ui/data-table/components/DataTableBody.tsx
+++ b/front/src/modules/ui/data-table/components/DataTableBody.tsx
@@ -27,6 +27,9 @@ export const DataTableBody = () => {
const tableRowIds = useRecoilValue(tableRowIdsState);
+ // eslint-disable-next-line no-console
+ console.log({ tableRowIds });
+
const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState);
const isFetchingDataTableData = useRecoilValue(isFetchingDataTableDataState);
diff --git a/front/src/modules/ui/data-table/components/DataTableCell.tsx b/front/src/modules/ui/data-table/components/DataTableCell.tsx
index 7025e7480a887..b7086f70e83d6 100644
--- a/front/src/modules/ui/data-table/components/DataTableCell.tsx
+++ b/front/src/modules/ui/data-table/components/DataTableCell.tsx
@@ -37,6 +37,9 @@ export const DataTableCell = ({ cellIndex }: { cellIndex: number }) => {
const updateEntityMutation = useContext(EntityUpdateMutationContext);
+ // eslint-disable-next-line no-console
+ console.log({ columnDefinition, currentRowId });
+
if (!columnDefinition || !currentRowId) {
return null;
}
diff --git a/front/src/modules/ui/data-table/components/DataTableRow.tsx b/front/src/modules/ui/data-table/components/DataTableRow.tsx
index 0515d6bc0f48b..d6f8eef9d214d 100644
--- a/front/src/modules/ui/data-table/components/DataTableRow.tsx
+++ b/front/src/modules/ui/data-table/components/DataTableRow.tsx
@@ -27,7 +27,8 @@ export const DataTableRow = forwardRef(
TableRecoilScopeContext,
);
const { currentRowSelected } = useCurrentRowSelected();
-
+ // eslint-disable-next-line no-console
+ console.log({ visibleTableColumns });
return (
{
const { fieldDefinition } = useContext(FieldContext);
+ // eslint-disable-next-line no-console
+ console.log({ fieldDefinition });
+
const { closeTableCell } = useTableCell();
const { moveLeft, moveRight, moveDown } = useMoveSoftFocus();
diff --git a/front/src/pages/companies/ObjectsTable.tsx b/front/src/pages/companies/ObjectsTable.tsx
new file mode 100644
index 0000000000000..75f909e390a41
--- /dev/null
+++ b/front/src/pages/companies/ObjectsTable.tsx
@@ -0,0 +1,58 @@
+import styled from '@emotion/styled';
+import { v4 } from 'uuid';
+
+import { ObjectTable } from '@/metadata/components/ObjectTable';
+import { DataTableActionBar } from '@/ui/data-table/action-bar/components/DataTableActionBar';
+import { DataTableContextMenu } from '@/ui/data-table/context-menu/components/DataTableContextMenu';
+import { TableRecoilScopeContext } from '@/ui/data-table/states/recoil-scope-contexts/TableRecoilScopeContext';
+import { IconBuildingSkyscraper } from '@/ui/icon';
+import { PageAddButton } from '@/ui/layout/components/PageAddButton';
+import { PageBody } from '@/ui/layout/components/PageBody';
+import { PageContainer } from '@/ui/layout/components/PageContainer';
+import { PageHeader } from '@/ui/layout/components/PageHeader';
+import { PageHotkeysEffect } from '@/ui/layout/components/PageHotkeysEffect';
+import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
+
+const StyledTableContainer = styled.div`
+ display: flex;
+ width: 100%;
+`;
+
+export const ObjectTablePage = ({
+ objectName,
+ objectNameSingular,
+}: {
+ objectNameSingular: string;
+ objectName: string;
+}) => {
+ const handleAddButtonClick = async () => {
+ const newCompanyId: string = v4();
+
+ // eslint-disable-next-line no-console
+ console.log('newCompanyId', newCompanyId);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};