Migrate to a monorepo structure (#2909)

This commit is contained in:
Charles Bochet
2023-12-10 18:10:54 +01:00
committed by GitHub
parent a70a9281eb
commit 5bdca9de6c
2304 changed files with 37152 additions and 25869 deletions

View File

@ -0,0 +1,272 @@
import { useParams } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { CompanyTeam } from '@/companies/components/CompanyTeam';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { filterAvailableFieldMetadataItem } from '@/object-record/utils/filterAvailableFieldMetadataItem';
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 { FileFolder, useUploadImageMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
import { useFindOneRecord } from '../hooks/useFindOneRecord';
import { useUpdateOneRecord } from '../hooks/useUpdateOneRecord';
export const RecordShowPage = () => {
const { objectNameSingular, objectRecordId } = useParams<{
objectNameSingular: string;
objectRecordId: string;
}>();
if (!objectNameSingular) {
throw new Error(`Object name is not defined`);
}
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { identifiersMapper } = useRelationPicker();
const { favorites, createFavorite, deleteFavorite } = useFavorites({
objectNamePlural: objectMetadataItem.namePlural,
});
const [, setEntityFields] = useRecoilState(
entityFieldsFamilyState(objectRecordId ?? ''),
);
const { record } = useFindOneRecord({
objectRecordId,
objectNameSingular,
onCompleted: (data) => {
setEntityFields(data);
},
});
const [uploadImage] = useUploadImageMutation();
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular,
});
const objectMetadataType =
objectMetadataItem?.nameSingular === 'company'
? 'Company'
: objectMetadataItem?.nameSingular === 'person'
? 'Person'
: 'Custom';
const useUpdateOneObjectRecordMutation: () => [
(params: any) => any,
any,
] = () => {
const updateEntity = ({
variables,
}: {
variables: {
where: { id: string };
data: {
[fieldName: string]: any;
};
};
}) => {
updateOneRecord?.({
idToUpdate: variables.where.id,
input: variables.data,
});
};
return [updateEntity, { loading: false }];
};
const isFavorite = objectNameSingular
? favorites.some((favorite) => favorite.recordId === record?.id)
: false;
const handleFavoriteButtonClick = async () => {
if (!objectNameSingular || !record) return;
if (isFavorite) deleteFavorite(record?.id);
else {
const additionalData =
objectNameSingular === 'person'
? {
labelIdentifier:
record.name.firstName + ' ' + record.name.lastName,
avatarUrl: record.avatarUrl,
avatarType: 'rounded',
link: `/object/personV2/${record.id}`,
recordId: record.id,
}
: objectNameSingular === 'company'
? {
labelIdentifier: record.name,
avatarUrl: getLogoUrlFromDomainName(record.domainName ?? ''),
avatarType: 'squared',
link: `/object/companyV2/${record.id}`,
recordId: record.id,
}
: {};
createFavorite(record.id, additionalData);
}
};
if (!record) return <></>;
const pageName =
objectNameSingular === 'person'
? record.name.firstName + ' ' + record.name.lastName
: record.name;
const recordIdentifiers = identifiersMapper?.(
record,
objectMetadataItem?.nameSingular ?? '',
);
const onUploadPicture = async (file: File) => {
if (objectNameSingular !== 'person') {
return;
}
const result = await uploadImage({
variables: {
file,
fileFolder: FileFolder.PersonPicture,
},
});
const avatarUrl = result?.data?.uploadImage;
if (!avatarUrl) {
return;
}
if (!updateOneRecord) {
return;
}
await updateOneRecord({
idToUpdate: record?.id,
input: {
avatarUrl,
},
});
};
return (
<PageContainer>
<PageTitle title={pageName} />
<PageHeader
title={pageName ?? ''}
hasBackButton
Icon={IconBuildingSkyscraper}
>
{objectMetadataType !== 'Custom' && (
<>
<PageFavoriteButton
isFavorite={isFavorite}
onClick={handleFavoriteButtonClick}
/>
<ShowPageAddButton
key="add"
entity={{
id: record.id,
type: objectMetadataType,
}}
/>
</>
)}
</PageHeader>
<PageBody>
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
<ShowPageContainer>
<ShowPageLeftContainer>
<ShowPageSummaryCard
id={record.id}
logoOrAvatar={recordIdentifiers?.avatarUrl}
title={recordIdentifiers?.name ?? 'No name'}
date={record.createdAt ?? ''}
renderTitleEditComponent={() => <></>}
avatarType={recordIdentifiers?.avatarType ?? 'rounded'}
onUploadPicture={
objectNameSingular === 'person' ? onUploadPicture : undefined
}
/>
<PropertyBox extraPadding={true}>
{objectMetadataItem &&
[...objectMetadataItem.fields]
.sort((a, b) =>
a.name === 'name' ? -1 : a.name.localeCompare(b.name),
)
.filter(filterAvailableFieldMetadataItem)
.map((metadataField, index) => {
return (
<FieldContext.Provider
key={record.id + metadataField.id}
value={{
entityId: record.id,
recoilScopeId: record.id + metadataField.id,
isLabelIdentifier: false,
fieldDefinition:
formatFieldMetadataItemAsColumnDefinition({
field: metadataField,
position: index,
objectMetadataItem,
}),
useUpdateEntityMutation:
useUpdateOneObjectRecordMutation,
hotkeyScope: InlineCellHotkeyScope.InlineCell,
}}
>
<RecordInlineCell />
</FieldContext.Provider>
);
})}
</PropertyBox>
{objectNameSingular === 'company' ? (
<>
<CompanyTeam company={record} />
</>
) : (
<></>
)}
</ShowPageLeftContainer>
<ShowPageRightContainer
entity={{
id: record.id,
// TODO: refacto
type:
objectMetadataItem?.nameSingular === 'company'
? 'Company'
: objectMetadataItem?.nameSingular === 'person'
? 'Person'
: 'Custom',
}}
timeline
tasks
notes
emails
/>
</ShowPageContainer>
</RecoilScope>
</PageBody>
</PageContainer>
);
};

View File

@ -0,0 +1,124 @@
import styled from '@emotion/styled';
import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCompanyImport';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordTable } from '@/object-record/record-table/components/RecordTable';
import { TableOptionsDropdownId } from '@/object-record/record-table/constants/TableOptionsDropdownId';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { TableOptionsDropdown } from '@/object-record/record-table/options/components/TableOptionsDropdown';
import { useSpreadsheetPersonImport } from '@/people/hooks/useSpreadsheetPersonImport';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
import { ViewBar } from '@/views/components/ViewBar';
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { RecordTableEffect } from './RecordTableEffect';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;
export const RecordTableContainer = ({
objectNamePlural,
createRecord,
}: {
objectNamePlural: string;
createRecord: () => void;
}) => {
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular,
});
const { openPersonSpreadsheetImport } = useSpreadsheetPersonImport();
const { openCompanySpreadsheetImport } = useSpreadsheetCompanyImport();
const recordTableId = objectNamePlural ?? '';
const viewBarId = objectNamePlural ?? '';
const { setTableFilters, setTableSorts, setTableColumns } = useRecordTable({
recordTableScopeId: recordTableId,
});
const updateEntity = ({
variables,
}: {
variables: {
where: { id: string };
data: {
[fieldName: string]: any;
};
};
}) => {
updateOneRecord?.({
idToUpdate: variables.where.id,
input: variables.data,
});
};
const handleImport = () => {
const openImport =
recordTableId === 'companies'
? openCompanySpreadsheetImport
: openPersonSpreadsheetImport;
openImport();
};
return (
<StyledContainer>
<SpreadsheetImportProvider>
<ViewBar
viewBarId={viewBarId}
optionsDropdownButton={
<TableOptionsDropdown
recordTableId={recordTableId}
onImport={
['companies', 'people'].includes(recordTableId)
? handleImport
: undefined
}
/>
}
optionsDropdownScopeId={TableOptionsDropdownId}
onViewFieldsChange={(viewFields) => {
setTableColumns(
mapViewFieldsToColumnDefinitions(viewFields, columnDefinitions),
);
}}
onViewFiltersChange={(viewFilters) => {
setTableFilters(mapViewFiltersToFilters(viewFilters));
}}
onViewSortsChange={(viewSorts) => {
setTableSorts(mapViewSortsToSorts(viewSorts));
}}
/>
</SpreadsheetImportProvider>
<RecordTableEffect recordTableId={recordTableId} viewBarId={viewBarId} />
{
<RecordTable
recordTableId={recordTableId}
viewBarId={viewBarId}
updateRecordMutation={updateEntity}
createRecord={createRecord}
/>
}
</StyledContainer>
);
};

View File

@ -0,0 +1,111 @@
import { useEffect } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
import { useViewBar } from '@/views/hooks/useViewBar';
import { ViewType } from '@/views/types/ViewType';
export const RecordTableEffect = ({
recordTableId,
viewBarId,
}: {
recordTableId: string;
viewBarId: string;
}) => {
const {
// Todo: do not infer objectNamePlural from recordTableId
scopeId: objectNamePlural,
setAvailableTableColumns,
setOnEntityCountChange,
setObjectMetadataConfig,
} = useRecordTable({ recordTableScopeId: recordTableId });
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const {
objectMetadataItem,
basePathToShowPage,
labelIdentifierFieldMetadataId,
} = useObjectMetadataItem({
objectNameSingular,
});
const { columnDefinitions, filterDefinitions, sortDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
const {
setAvailableSortDefinitions,
setAvailableFilterDefinitions,
setAvailableFieldDefinitions,
setViewType,
setViewObjectMetadataId,
setEntityCountInCurrentView,
} = useViewBar({ viewBarId });
useEffect(() => {
if (basePathToShowPage && labelIdentifierFieldMetadataId) {
setObjectMetadataConfig?.({
basePathToShowPage,
labelIdentifierFieldMetadataId,
});
}
}, [
basePathToShowPage,
objectMetadataItem,
labelIdentifierFieldMetadataId,
setObjectMetadataConfig,
]);
useEffect(() => {
if (!objectMetadataItem) {
return;
}
setViewObjectMetadataId?.(objectMetadataItem.id);
setViewType?.(ViewType.Table);
setAvailableSortDefinitions?.(sortDefinitions);
setAvailableFilterDefinitions?.(filterDefinitions);
setAvailableFieldDefinitions?.(columnDefinitions);
const availableTableColumns = columnDefinitions.filter(
filterAvailableTableColumns,
);
setAvailableTableColumns(availableTableColumns);
}, [
setViewObjectMetadataId,
setViewType,
columnDefinitions,
setAvailableSortDefinitions,
setAvailableFilterDefinitions,
setAvailableFieldDefinitions,
objectMetadataItem,
sortDefinitions,
filterDefinitions,
setAvailableTableColumns,
]);
const { setActionBarEntries, setContextMenuEntries } =
useRecordTableContextMenuEntries({
recordTableScopeId: recordTableId,
});
useEffect(() => {
setActionBarEntries?.();
setContextMenuEntries?.();
}, [setActionBarEntries, setContextMenuEntries]);
useEffect(() => {
setOnEntityCountChange(
() => (entityCount: number) => setEntityCountInCurrentView(entityCount),
);
}, [setEntityCountInCurrentView, setOnEntityCountChange]);
return <></>;
};

View File

@ -0,0 +1,89 @@
import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import styled from '@emotion/styled';
import { isNonEmptyString } from '@sniptt/guards';
import { useOnboardingStatus } from '@/auth/hooks/useOnboardingStatus';
import { OnboardingStatus } from '@/auth/utils/getOnboardingStatus';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar';
import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu';
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { PageBody } from '@/ui/layout/page/PageBody';
import { PageContainer } from '@/ui/layout/page/PageContainer';
import { PageHeader } from '@/ui/layout/page/PageHeader';
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
import { RecordTableContainer } from './RecordTableContainer';
const StyledTableContainer = styled.div`
display: flex;
height: 100%;
width: 100%;
`;
export const RecordTablePage = () => {
const objectNamePlural = useParams().objectNamePlural ?? '';
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
});
const onboardingStatus = useOnboardingStatus();
const navigate = useNavigate();
const { icons } = useLazyLoadIcons();
const { findObjectMetadataItemByNamePlural } =
useObjectMetadataItemForSettings();
useEffect(() => {
if (
!isNonEmptyString(objectNamePlural) &&
onboardingStatus === OnboardingStatus.Completed
) {
navigate('/');
}
}, [objectNamePlural, navigate, onboardingStatus]);
const { createOneRecord: createOneObject } = useCreateOneRecord({
objectNameSingular,
});
const handleAddButtonClick = async () => {
await createOneObject?.({});
};
return (
<PageContainer>
<PageHeader
title={
objectNamePlural.charAt(0).toUpperCase() + objectNamePlural.slice(1)
}
Icon={
icons[
findObjectMetadataItemByNamePlural(objectNamePlural)!.icon ??
'Icon123'
]
}
>
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
<PageAddButton onClick={handleAddButtonClick} />
</PageHeader>
<PageBody>
<StyledTableContainer>
<RecordTableContainer
objectNamePlural={objectNamePlural}
createRecord={handleAddButtonClick}
/>
</StyledTableContainer>
<RecordTableActionBar />
<RecordTableContextMenu />
</PageBody>
</PageContainer>
);
};