Feat/show page metadata (#2234)
* Fix view fetch bug * Finished types * Removed console.log * Fixed todo * Working Object Show Page * Minor fixes * Fix custom object requests pending (#2240) * Fix custom object requests pending * fix typo * Fix various bugs * Typo * Fix * Fix * Fix --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@ -4,6 +4,7 @@ module.exports = {
|
|||||||
project: 'tsconfig.json',
|
project: 'tsconfig.json',
|
||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
|
ecmaVersion: "2023"
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
'@typescript-eslint/eslint-plugin',
|
'@typescript-eslint/eslint-plugin',
|
||||||
|
|||||||
@ -22,6 +22,20 @@ module.exports = {
|
|||||||
'@': path.resolve(__dirname, 'src/modules'),
|
'@': path.resolve(__dirname, 'src/modules'),
|
||||||
'@testing': path.resolve(__dirname, 'src/testing'),
|
'@testing': path.resolve(__dirname, 'src/testing'),
|
||||||
},
|
},
|
||||||
|
mode: 'extends',
|
||||||
|
// TODO: remove this workaround by resolving source map errors with @sniptt/guards
|
||||||
|
configure: {
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
enforce: "pre",
|
||||||
|
use: ["source-map-loader"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ignoreWarnings: [/Failed to parse source map/],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
jest: {
|
jest: {
|
||||||
configure: {
|
configure: {
|
||||||
|
|||||||
@ -180,7 +180,7 @@
|
|||||||
"storybook-addon-cookie": "^3.0.1",
|
"storybook-addon-cookie": "^3.0.1",
|
||||||
"storybook-addon-pseudo-states": "^2.1.0",
|
"storybook-addon-pseudo-states": "^2.1.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^5.2.2",
|
||||||
"webpack": "^5.75.0"
|
"webpack": "^5.75.0"
|
||||||
},
|
},
|
||||||
"msw": {
|
"msw": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { ObjectShowPage } from '@/metadata/components/ObjectShowPage';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
|
import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
|
||||||
@ -63,6 +64,7 @@ export const App = () => {
|
|||||||
|
|
||||||
<Route path={AppPath.OpportunitiesPage} element={<Opportunities />} />
|
<Route path={AppPath.OpportunitiesPage} element={<Opportunities />} />
|
||||||
<Route path={AppPath.ObjectTablePage} element={<ObjectTablePage />} />
|
<Route path={AppPath.ObjectTablePage} element={<ObjectTablePage />} />
|
||||||
|
<Route path={AppPath.ObjectShowPage} element={<ObjectShowPage />} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={AppPath.SettingsCatchAll}
|
path={AppPath.SettingsCatchAll}
|
||||||
|
|||||||
@ -10,7 +10,10 @@ import { useFindManyObjects } from '../hooks/useFindManyObjects';
|
|||||||
import { useSetObjectDataTableData } from '../hooks/useSetDataTableData';
|
import { useSetObjectDataTableData } from '../hooks/useSetDataTableData';
|
||||||
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
|
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
|
||||||
|
|
||||||
export type ObjectDataTableEffectProps = MetadataObjectIdentifier;
|
export type ObjectDataTableEffectProps = Pick<
|
||||||
|
MetadataObjectIdentifier,
|
||||||
|
'objectNamePlural'
|
||||||
|
>;
|
||||||
|
|
||||||
export const ObjectDataTableEffect = ({
|
export const ObjectDataTableEffect = ({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
@ -33,10 +36,11 @@ export const ObjectDataTableEffect = ({
|
|||||||
const tableRecoilScopeId = useRecoilScopeId(TableRecoilScopeContext);
|
const tableRecoilScopeId = useRecoilScopeId(TableRecoilScopeContext);
|
||||||
const handleViewSelect = useRecoilCallback(
|
const handleViewSelect = useRecoilCallback(
|
||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
async (viewId: string) => {
|
(viewId: string) => {
|
||||||
const currentView = await snapshot.getPromise(
|
const currentView = snapshot.getLoadable(
|
||||||
currentViewIdScopedState({ scopeId: tableRecoilScopeId }),
|
currentViewIdScopedState({ scopeId: tableRecoilScopeId }),
|
||||||
);
|
).getValue()
|
||||||
|
|
||||||
if (currentView === viewId) {
|
if (currentView === viewId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
159
front/src/modules/metadata/components/ObjectShowPage.tsx
Normal file
159
front/src/modules/metadata/components/ObjectShowPage.tsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
|
||||||
|
import { FieldContext } from '@/ui/data/field/contexts/FieldContext';
|
||||||
|
import { entityFieldsFamilyState } from '@/ui/data/field/states/entityFieldsFamilyState';
|
||||||
|
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 { PageBody } from '@/ui/layout/page/PageBody';
|
||||||
|
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||||
|
import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton';
|
||||||
|
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||||
|
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
|
||||||
|
import { ShowPageAddButton } from '@/ui/layout/show-page/components/ShowPageAddButton';
|
||||||
|
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||||
|
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||||
|
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||||
|
import { ShowPageRecoilScopeContext } from '@/ui/layout/states/ShowPageRecoilScopeContext';
|
||||||
|
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||||
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
|
import { useFindOneMetadataObject } from '../hooks/useFindOneMetadataObject';
|
||||||
|
import { useFindOneObject } from '../hooks/useFindOneObject';
|
||||||
|
import { useUpdateOneObject } from '../hooks/useUpdateOneObject';
|
||||||
|
import { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition';
|
||||||
|
|
||||||
|
export const ObjectShowPage = () => {
|
||||||
|
const { objectNameSingular, objectId } = useParams<{
|
||||||
|
objectNameSingular: string;
|
||||||
|
objectId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { foundMetadataObject } = useFindOneMetadataObject({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [, setEntityFields] = useRecoilState(
|
||||||
|
entityFieldsFamilyState(objectId ?? ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { object } = useFindOneObject({
|
||||||
|
objectId: objectId,
|
||||||
|
objectNameSingular,
|
||||||
|
onCompleted: (data) => {
|
||||||
|
setEntityFields(data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => {
|
||||||
|
const { updateOneObject } = useUpdateOneObject({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateEntity = ({
|
||||||
|
variables,
|
||||||
|
}: {
|
||||||
|
variables: {
|
||||||
|
where: { id: string };
|
||||||
|
data: {
|
||||||
|
[fieldName: string]: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
updateOneObject?.({
|
||||||
|
idToUpdate: variables.where.id,
|
||||||
|
input: variables.data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return [updateEntity, { loading: false }];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFavoriteButtonClick = async () => {
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!object) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<PageTitle title={object.name || 'No Name'} />
|
||||||
|
<PageHeader
|
||||||
|
title={object.name ?? ''}
|
||||||
|
hasBackButton
|
||||||
|
Icon={IconBuildingSkyscraper}
|
||||||
|
>
|
||||||
|
<PageFavoriteButton
|
||||||
|
isFavorite={false}
|
||||||
|
onClick={handleFavoriteButtonClick}
|
||||||
|
/>
|
||||||
|
<ShowPageAddButton
|
||||||
|
key="add"
|
||||||
|
entity={{
|
||||||
|
id: object.id,
|
||||||
|
type: ActivityTargetableEntityType.Company,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PageHeader>
|
||||||
|
<PageBody>
|
||||||
|
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||||
|
<ShowPageContainer>
|
||||||
|
<ShowPageLeftContainer>
|
||||||
|
<ShowPageSummaryCard
|
||||||
|
id={object.id}
|
||||||
|
logoOrAvatar={''}
|
||||||
|
title={object.name ?? 'No name'}
|
||||||
|
date={object.createdAt ?? ''}
|
||||||
|
renderTitleEditComponent={() => <></>}
|
||||||
|
avatarType="squared"
|
||||||
|
/>
|
||||||
|
<PropertyBox extraPadding={true}>
|
||||||
|
{foundMetadataObject?.fields
|
||||||
|
.toSorted((a, b) =>
|
||||||
|
DateTime.fromISO(a.createdAt)
|
||||||
|
.diff(DateTime.fromISO(b.createdAt))
|
||||||
|
.toMillis(),
|
||||||
|
)
|
||||||
|
.map((metadataField, index) => {
|
||||||
|
return (
|
||||||
|
<FieldContext.Provider
|
||||||
|
key={object.id + metadataField.id}
|
||||||
|
value={{
|
||||||
|
entityId: object.id,
|
||||||
|
recoilScopeId: object.id + metadataField.id,
|
||||||
|
fieldDefinition:
|
||||||
|
formatMetadataFieldAsColumnDefinition({
|
||||||
|
field: metadataField,
|
||||||
|
index: index,
|
||||||
|
metadataObject: foundMetadataObject,
|
||||||
|
}),
|
||||||
|
useUpdateEntityMutation: useUpdateOneObjectMutation,
|
||||||
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InlineCell />
|
||||||
|
</FieldContext.Provider>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</PropertyBox>
|
||||||
|
</ShowPageLeftContainer>
|
||||||
|
<ShowPageRightContainer
|
||||||
|
entity={{
|
||||||
|
id: object.id,
|
||||||
|
type: ActivityTargetableEntityType.Company,
|
||||||
|
}}
|
||||||
|
timeline
|
||||||
|
tasks
|
||||||
|
notes
|
||||||
|
emails
|
||||||
|
/>
|
||||||
|
</ShowPageContainer>
|
||||||
|
</RecoilScope>
|
||||||
|
</PageBody>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -6,7 +6,10 @@ import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
|
|||||||
|
|
||||||
import { ObjectDataTableEffect } from './ObjectDataTableEffect';
|
import { ObjectDataTableEffect } from './ObjectDataTableEffect';
|
||||||
|
|
||||||
export type ObjectTableProps = MetadataObjectIdentifier;
|
export type ObjectTableProps = Pick<
|
||||||
|
MetadataObjectIdentifier,
|
||||||
|
'objectNamePlural'
|
||||||
|
>;
|
||||||
|
|
||||||
export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => {
|
export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => {
|
||||||
const { updateOneObject } = useUpdateOneObject({
|
const { updateOneObject } = useUpdateOneObject({
|
||||||
|
|||||||
@ -24,7 +24,10 @@ const StyledTableContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type ObjectTablePageProps = MetadataObjectIdentifier;
|
export type ObjectTablePageProps = Pick<
|
||||||
|
MetadataObjectIdentifier,
|
||||||
|
'objectNamePlural'
|
||||||
|
>;
|
||||||
|
|
||||||
export const ObjectTablePage = () => {
|
export const ObjectTablePage = () => {
|
||||||
const objectNamePlural = useParams().objectNamePlural ?? '';
|
const objectNamePlural = useParams().objectNamePlural ?? '';
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { gql } from '@apollo/client';
|
|||||||
|
|
||||||
export const FIND_MANY_METADATA_OBJECTS = gql`
|
export const FIND_MANY_METADATA_OBJECTS = gql`
|
||||||
query MetadataObjects {
|
query MetadataObjects {
|
||||||
objects {
|
objects(paging: { first: 100 }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
@ -17,7 +17,7 @@ export const FIND_MANY_METADATA_OBJECTS = gql`
|
|||||||
isActive
|
isActive
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
fields {
|
fields(paging: { first: 100 }) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
|
|||||||
@ -169,8 +169,8 @@ export const useCreateNewTempsCustomObject = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const createdFields = [
|
const createdFields = [
|
||||||
emailFieldData,
|
|
||||||
nameFieldData,
|
nameFieldData,
|
||||||
|
emailFieldData,
|
||||||
cityFieldData,
|
cityFieldData,
|
||||||
phoneFieldData,
|
phoneFieldData,
|
||||||
twitterFieldData,
|
twitterFieldData,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useFindOneMetadataObject } from './useFindOneMetadataObject';
|
|||||||
|
|
||||||
export const useCreateOneObject = ({
|
export const useCreateOneObject = ({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
}: MetadataObjectIdentifier) => {
|
}: Pick<MetadataObjectIdentifier, 'objectNamePlural'>) => {
|
||||||
const {
|
const {
|
||||||
foundMetadataObject,
|
foundMetadataObject,
|
||||||
objectNotFoundInMetadata,
|
objectNotFoundInMetadata,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useFindOneMetadataObject } from './useFindOneMetadataObject';
|
|||||||
|
|
||||||
export const useDeleteOneObject = ({
|
export const useDeleteOneObject = ({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
}: MetadataObjectIdentifier) => {
|
}: Pick<MetadataObjectIdentifier, 'objectNamePlural'>) => {
|
||||||
const {
|
const {
|
||||||
foundMetadataObject,
|
foundMetadataObject,
|
||||||
objectNotFoundInMetadata,
|
objectNotFoundInMetadata,
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const useFindManyObjects = <
|
|||||||
ObjectType extends { id: string } & Record<string, any>,
|
ObjectType extends { id: string } & Record<string, any>,
|
||||||
>({
|
>({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
}: MetadataObjectIdentifier) => {
|
}: Pick<MetadataObjectIdentifier, 'objectNamePlural'>) => {
|
||||||
const { foundMetadataObject, objectNotFoundInMetadata, findManyQuery } =
|
const { foundMetadataObject, objectNotFoundInMetadata, findManyQuery } =
|
||||||
useFindOneMetadataObject({
|
useFindOneMetadataObject({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
@ -28,10 +28,12 @@ export const useFindManyObjects = <
|
|||||||
|
|
||||||
const objects = useMemo(
|
const objects = useMemo(
|
||||||
() =>
|
() =>
|
||||||
formatPagedObjectsToObjects({
|
objectNamePlural
|
||||||
|
? formatPagedObjectsToObjects({
|
||||||
pagedObjects: data,
|
pagedObjects: data,
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
}),
|
})
|
||||||
|
: [],
|
||||||
[data, objectNamePlural],
|
[data, objectNamePlural],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -7,16 +7,20 @@ import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
|
|||||||
import { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition';
|
import { formatMetadataFieldAsColumnDefinition } from '../utils/formatMetadataFieldAsColumnDefinition';
|
||||||
import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation';
|
import { generateCreateOneObjectMutation } from '../utils/generateCreateOneObjectMutation';
|
||||||
import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery';
|
import { generateFindManyCustomObjectsQuery } from '../utils/generateFindManyCustomObjectsQuery';
|
||||||
|
import { generateFindOneCustomObjectQuery } from '../utils/generateFindOneCustomObjectQuery';
|
||||||
|
|
||||||
import { useFindManyMetadataObjects } from './useFindManyMetadataObjects';
|
import { useFindManyMetadataObjects } from './useFindManyMetadataObjects';
|
||||||
|
|
||||||
export const useFindOneMetadataObject = ({
|
export const useFindOneMetadataObject = ({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
|
objectNameSingular,
|
||||||
}: MetadataObjectIdentifier) => {
|
}: MetadataObjectIdentifier) => {
|
||||||
const { metadataObjects, loading } = useFindManyMetadataObjects();
|
const { metadataObjects, loading } = useFindManyMetadataObjects();
|
||||||
|
|
||||||
const foundMetadataObject = metadataObjects.find(
|
const foundMetadataObject = metadataObjects.find(
|
||||||
(object) => object.namePlural === objectNamePlural,
|
(object) =>
|
||||||
|
object.namePlural === objectNamePlural ||
|
||||||
|
object.nameSingular === objectNameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectNotFoundInMetadata =
|
const objectNotFoundInMetadata =
|
||||||
@ -28,6 +32,7 @@ export const useFindOneMetadataObject = ({
|
|||||||
formatMetadataFieldAsColumnDefinition({
|
formatMetadataFieldAsColumnDefinition({
|
||||||
index,
|
index,
|
||||||
field,
|
field,
|
||||||
|
metadataObject: foundMetadataObject,
|
||||||
}),
|
}),
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
@ -41,6 +46,16 @@ export const useFindOneMetadataObject = ({
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const findOneQuery = foundMetadataObject
|
||||||
|
? generateFindOneCustomObjectQuery({
|
||||||
|
metadataObject: foundMetadataObject,
|
||||||
|
})
|
||||||
|
: gql`
|
||||||
|
query EmptyQuery {
|
||||||
|
empty
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const createOneMutation = foundMetadataObject
|
const createOneMutation = foundMetadataObject
|
||||||
? generateCreateOneObjectMutation({
|
? generateCreateOneObjectMutation({
|
||||||
metadataObject: foundMetadataObject,
|
metadataObject: foundMetadataObject,
|
||||||
@ -67,6 +82,7 @@ export const useFindOneMetadataObject = ({
|
|||||||
objectNotFoundInMetadata,
|
objectNotFoundInMetadata,
|
||||||
columnDefinitions,
|
columnDefinitions,
|
||||||
findManyQuery,
|
findManyQuery,
|
||||||
|
findOneQuery,
|
||||||
createOneMutation,
|
createOneMutation,
|
||||||
deleteOneMutation,
|
deleteOneMutation,
|
||||||
loading,
|
loading,
|
||||||
|
|||||||
46
front/src/modules/metadata/hooks/useFindOneObject.ts
Normal file
46
front/src/modules/metadata/hooks/useFindOneObject.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { useQuery } from '@apollo/client';
|
||||||
|
|
||||||
|
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
|
||||||
|
|
||||||
|
import { useFindOneMetadataObject } from './useFindOneMetadataObject';
|
||||||
|
|
||||||
|
export const useFindOneObject = <
|
||||||
|
ObjectType extends { id: string } & Record<string, any>,
|
||||||
|
>({
|
||||||
|
objectNameSingular,
|
||||||
|
objectId,
|
||||||
|
onCompleted,
|
||||||
|
}: Pick<MetadataObjectIdentifier, 'objectNameSingular'> & {
|
||||||
|
objectId: string | undefined;
|
||||||
|
onCompleted?: (data: ObjectType) => void;
|
||||||
|
}) => {
|
||||||
|
const { foundMetadataObject, objectNotFoundInMetadata, findOneQuery } =
|
||||||
|
useFindOneMetadataObject({
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data, loading, error } = useQuery<
|
||||||
|
{ [nameSingular: string]: ObjectType },
|
||||||
|
{ objectId: string }
|
||||||
|
>(findOneQuery, {
|
||||||
|
skip: !foundMetadataObject || !objectId,
|
||||||
|
variables: {
|
||||||
|
objectId: objectId ?? '',
|
||||||
|
},
|
||||||
|
onCompleted: (data) => {
|
||||||
|
if (onCompleted && objectNameSingular) {
|
||||||
|
onCompleted(data[objectNameSingular]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const object =
|
||||||
|
objectNameSingular && data ? data[objectNameSingular] : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
object,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
objectNotFoundInMetadata,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { gql, useMutation } from '@apollo/client';
|
import { gql, useMutation } from '@apollo/client';
|
||||||
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
|
||||||
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
|
import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier';
|
||||||
import { generateUpdateOneObjectMutation } from '../utils/generateUpdateOneObjectMutation';
|
import { generateUpdateOneObjectMutation } from '../utils/generateUpdateOneObjectMutation';
|
||||||
@ -7,10 +8,12 @@ import { useFindOneMetadataObject } from './useFindOneMetadataObject';
|
|||||||
|
|
||||||
export const useUpdateOneObject = ({
|
export const useUpdateOneObject = ({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
|
objectNameSingular,
|
||||||
}: MetadataObjectIdentifier) => {
|
}: MetadataObjectIdentifier) => {
|
||||||
const { foundMetadataObject, objectNotFoundInMetadata } =
|
const { foundMetadataObject, objectNotFoundInMetadata, findManyQuery } =
|
||||||
useFindOneMetadataObject({
|
useFindOneMetadataObject({
|
||||||
objectNamePlural,
|
objectNamePlural,
|
||||||
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const generatedMutation = foundMetadataObject
|
const generatedMutation = foundMetadataObject
|
||||||
@ -24,7 +27,9 @@ export const useUpdateOneObject = ({
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// TODO: type this with a minimal type at least with Record<string, any>
|
// TODO: type this with a minimal type at least with Record<string, any>
|
||||||
const [mutate] = useMutation(generatedMutation);
|
const [mutate] = useMutation(generatedMutation, {
|
||||||
|
refetchQueries: [getOperationName(findManyQuery) ?? ''],
|
||||||
|
});
|
||||||
|
|
||||||
const updateOneObject = foundMetadataObject
|
const updateOneObject = foundMetadataObject
|
||||||
? ({
|
? ({
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export type MetadataObjectIdentifier = {
|
export type MetadataObjectIdentifier = {
|
||||||
objectNamePlural: string;
|
objectNamePlural?: string;
|
||||||
|
objectNameSingular?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,9 +20,11 @@ const parseFieldType = (fieldType: string): FieldType => {
|
|||||||
export const formatMetadataFieldAsColumnDefinition = ({
|
export const formatMetadataFieldAsColumnDefinition = ({
|
||||||
index,
|
index,
|
||||||
field,
|
field,
|
||||||
|
metadataObject,
|
||||||
}: {
|
}: {
|
||||||
index: number;
|
index: number;
|
||||||
field: MetadataObject['fields'][0];
|
field: MetadataObject['fields'][0];
|
||||||
|
metadataObject: Omit<MetadataObject, 'fields'>;
|
||||||
}): ColumnDefinition<FieldMetadata> => ({
|
}): ColumnDefinition<FieldMetadata> => ({
|
||||||
index,
|
index,
|
||||||
key: field.name,
|
key: field.name,
|
||||||
@ -35,4 +37,5 @@ export const formatMetadataFieldAsColumnDefinition = ({
|
|||||||
},
|
},
|
||||||
Icon: IconBrandLinkedin,
|
Icon: IconBrandLinkedin,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
|
basePathToShowPage: `/object/${metadataObject.nameSingular}/`,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
import { MetadataObject } from '../types/MetadataObject';
|
||||||
|
|
||||||
|
import { mapFieldMetadataToGraphQLQuery } from './mapFieldMetadataToGraphQLQuery';
|
||||||
|
|
||||||
|
export const generateFindOneCustomObjectQuery = ({
|
||||||
|
metadataObject,
|
||||||
|
}: {
|
||||||
|
metadataObject: MetadataObject;
|
||||||
|
}) => {
|
||||||
|
return gql`
|
||||||
|
query FindOne${metadataObject.nameSingular}($objectId: UUID!) {
|
||||||
|
${metadataObject.nameSingular}(filter: {
|
||||||
|
id: {
|
||||||
|
eq: $objectId
|
||||||
|
}
|
||||||
|
}){
|
||||||
|
id
|
||||||
|
${metadataObject.fields.map(mapFieldMetadataToGraphQLQuery).join('\n')}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
@ -31,7 +31,8 @@ export const SettingsObjectFieldDataType = ({
|
|||||||
value,
|
value,
|
||||||
}: SettingsObjectFieldDataTypeProps) => {
|
}: SettingsObjectFieldDataTypeProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { label, Icon } = dataTypes[value];
|
|
||||||
|
const { label, Icon } = dataTypes?.[value];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDataType value={value}>
|
<StyledDataType value={value}>
|
||||||
|
|||||||
@ -37,6 +37,18 @@ export const SettingsObjectFieldItemTableRow = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { Icon } = useLazyLoadIcon(fieldItem.icon ?? '');
|
const { Icon } = useLazyLoadIcon(fieldItem.icon ?? '');
|
||||||
|
|
||||||
|
// TODO: parse with zod and merge types with FieldType (create a subset of FieldType for example)
|
||||||
|
const fieldDataTypeIsSupported = [
|
||||||
|
'text',
|
||||||
|
'number',
|
||||||
|
'boolean',
|
||||||
|
'url',
|
||||||
|
].includes(fieldItem.type);
|
||||||
|
|
||||||
|
if (!fieldDataTypeIsSupported) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledObjectFieldTableRow>
|
<StyledObjectFieldTableRow>
|
||||||
<StyledNameTableCell>
|
<StyledNameTableCell>
|
||||||
|
|||||||
@ -19,6 +19,8 @@ export enum AppPath {
|
|||||||
OpportunitiesPage = '/opportunities',
|
OpportunitiesPage = '/opportunities',
|
||||||
ObjectTablePage = '/objects/:objectNamePlural',
|
ObjectTablePage = '/objects/:objectNamePlural',
|
||||||
|
|
||||||
|
ObjectShowPage = '/object/:objectNameSingular/:objectId',
|
||||||
|
|
||||||
SettingsCatchAll = `/settings/*`,
|
SettingsCatchAll = `/settings/*`,
|
||||||
DevelopersCatchAll = `/developers/*`,
|
DevelopersCatchAll = `/developers/*`,
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import { isTableCellInEditModeFamilyState } from '../states/isTableCellInEditMod
|
|||||||
export const useCloseCurrentTableCellInEditMode = () =>
|
export const useCloseCurrentTableCellInEditMode = () =>
|
||||||
useRecoilCallback(({ set, snapshot }) => {
|
useRecoilCallback(({ set, snapshot }) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
const currentTableCellInEditModePosition = await snapshot.getPromise(
|
const currentTableCellInEditModePosition = snapshot
|
||||||
currentTableCellInEditModePositionState,
|
.getLoadable(currentTableCellInEditModePositionState)
|
||||||
);
|
.valueOrThrow();
|
||||||
|
|
||||||
set(
|
set(
|
||||||
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
|
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export const BoardOptionsDropdownContent = ({
|
|||||||
const viewEditMode = snapshot
|
const viewEditMode = snapshot
|
||||||
.getLoadable(viewEditModeScopedState({ scopeId: boardRecoilScopeId }))
|
.getLoadable(viewEditModeScopedState({ scopeId: boardRecoilScopeId }))
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
if (!viewEditMode) {
|
if (!viewEditMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,9 +23,10 @@ export const useSetHotkeyScope = () =>
|
|||||||
useRecoilCallback(
|
useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
async (hotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
|
async (hotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
|
||||||
const currentHotkeyScope = await snapshot.getPromise(
|
const currentHotkeyScope = snapshot
|
||||||
currentHotkeyScopeState,
|
.getLoadable(currentHotkeyScopeState)
|
||||||
);
|
.valueOrThrow();
|
||||||
|
|
||||||
if (currentHotkeyScope.scope === hotkeyScopeToSet) {
|
if (currentHotkeyScope.scope === hotkeyScopeToSet) {
|
||||||
if (!isDefined(customScopes)) {
|
if (!isDefined(customScopes)) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
import { GET_VIEW_FIELDS } from '../../graphql/queries/getViewFields';
|
import { GET_VIEW_FIELDS } from '../../graphql/queries/getViewFields';
|
||||||
|
import { GET_VIEWS } from '@/views/graphql/queries/getViews';
|
||||||
|
|
||||||
export const toViewFieldInput = (
|
export const toViewFieldInput = (
|
||||||
objectId: string,
|
objectId: string,
|
||||||
@ -92,6 +93,7 @@ export const useViewFields = (viewScopeId: string) => {
|
|||||||
viewId_key: { key: viewField.key, viewId: currentViewId },
|
viewId_key: { key: viewField.key, viewId: currentViewId },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext", "ES2023", "ES2023.Array"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|||||||
@ -18729,10 +18729,10 @@ typedarray@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||||
|
|
||||||
typescript@^4.9.3:
|
typescript@^5.2.2:
|
||||||
version "4.9.5"
|
version "5.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
|
||||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
||||||
|
|
||||||
ua-parser-js@^1.0.35:
|
ua-parser-js@^1.0.35:
|
||||||
version "1.0.35"
|
version "1.0.35"
|
||||||
|
|||||||
Reference in New Issue
Block a user