diff --git a/front/src/config/index.ts b/front/src/config/index.ts index 3e86884ac..9a8043a58 100644 --- a/front/src/config/index.ts +++ b/front/src/config/index.ts @@ -1,6 +1,7 @@ declare global { interface Window { _env_?: Record; + __APOLLO_CLIENT__?: any; } } diff --git a/front/src/modules/activities/components/ActivityComments.tsx b/front/src/modules/activities/components/ActivityComments.tsx index 33cecec34..cee535afc 100644 --- a/front/src/modules/activities/components/ActivityComments.tsx +++ b/front/src/modules/activities/components/ActivityComments.tsx @@ -102,8 +102,6 @@ export const ActivityComments = ({ }); }; - console.log('asd', { activity, comments }); - return ( <> {comments.length > 0 && ( diff --git a/front/src/modules/apollo/components/ApolloProvider.tsx b/front/src/modules/apollo/components/ApolloProvider.tsx index 42ad2edb9..21a501528 100644 --- a/front/src/modules/apollo/components/ApolloProvider.tsx +++ b/front/src/modules/apollo/components/ApolloProvider.tsx @@ -5,6 +5,9 @@ import { useApolloFactory } from '@/apollo/hooks/useApolloFactory'; export const ApolloProvider = ({ children }: React.PropsWithChildren) => { const apolloClient = useApolloFactory(); + // This will attach the right apollo client to Apollo Dev Tools + window.__APOLLO_CLIENT__ = apolloClient; + return ( {children} ); diff --git a/front/src/modules/apollo/services/apollo.factory.ts b/front/src/modules/apollo/services/apollo.factory.ts index c3b371dd3..54f605f5e 100644 --- a/front/src/modules/apollo/services/apollo.factory.ts +++ b/front/src/modules/apollo/services/apollo.factory.ts @@ -83,6 +83,18 @@ export class ApolloFactory implements ApolloManager { onErrorCb?.(graphQLErrors); for (const graphQLError of graphQLErrors) { + if (graphQLError.message === 'Unauthorized') { + return fromPromise( + renewToken(uri, this.tokenPair) + .then((tokens) => { + onTokenPairChange?.(tokens); + }) + .catch(() => { + onUnauthenticatedError?.(); + }), + ).flatMap(() => forward(operation)); + } + switch (graphQLError?.extensions?.code) { case 'UNAUTHENTICATED': { return fromPromise( diff --git a/front/src/modules/command-menu/components/CommandMenu.tsx b/front/src/modules/command-menu/components/CommandMenu.tsx index a4590991a..566fcd107 100644 --- a/front/src/modules/command-menu/components/CommandMenu.tsx +++ b/front/src/modules/command-menu/components/CommandMenu.tsx @@ -2,8 +2,13 @@ import { useState } from 'react'; import { useRecoilValue } from 'recoil'; import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; +import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; +import { Person } from '@/people/types/Person'; +import { IconNotes } from '@/ui/display/icon'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; +import { Avatar } from '@/users/components/Avatar'; +import { getLogoUrlFromDomainName } from '~/utils'; import { useCommandMenu } from '../hooks/useCommandMenu'; import { commandMenuCommandsState } from '../states/commandMenuCommandsState'; @@ -36,47 +41,38 @@ export const CommandMenu = () => { [openCommandMenu, setSearch], ); - // const { data: peopleData } = useSearchPeopleQuery({ - // skip: !isCommandMenuOpened, - // variables: { - // where: { - // OR: [ - // { firstName: { contains: search, mode: QueryMode.Insensitive } }, - // { lastName: { contains: search, mode: QueryMode.Insensitive } }, - // ], - // }, - // limit: 3, - // }, - // }); + const { objects: people } = useFindManyObjectRecords({ + skip: !isCommandMenuOpened, + objectNamePlural: 'people', + filter: { + or: [ + { name: { firstName: { like: `%${search}%` } } }, + { name: { firstName: { like: `%${search}%` } } }, + ], + }, + limit: 3, + }); - // const people = peopleData?.searchResults ?? []; + const { objects: companies } = useFindManyObjectRecords({ + skip: !isCommandMenuOpened, + objectNamePlural: 'companies', + filter: { + name: { like: `%${search}%` }, + }, + limit: 3, + }); - // const { data: companyData } = useSearchCompanyQuery({ - // skip: !isCommandMenuOpened, - // variables: { - // where: { - // OR: [{ name: { contains: search, mode: QueryMode.Insensitive } }], - // }, - // limit: 3, - // }, - // }); - - // const companies = companyData?.searchResults ?? []; - - // const { data: activityData } = useSearchActivityQuery({ - // skip: !isCommandMenuOpened, - // variables: { - // where: { - // OR: [ - // { title: { contains: search, mode: QueryMode.Insensitive } }, - // { body: { contains: search, mode: QueryMode.Insensitive } }, - // ], - // }, - // limit: 3, - // }, - // }); - - // const activities = activityData?.searchResults ?? []; + const { objects: activities } = useFindManyObjectRecords({ + skip: !isCommandMenuOpened, + objectNamePlural: 'activities', + filter: { + or: [ + { title: { like: `%${search}%` } }, + { body: { like: `%${search}%` } }, + ], + }, + limit: 3, + }); const checkInShortcuts = (cmd: Command, search: string) => { return (cmd.firstHotKey + (cmd.secondHotKey ?? '')) @@ -149,12 +145,12 @@ export const CommandMenu = () => { /> ))} - {/* + {people.map((person) => ( ( { ( { onClick={() => openActivityRightDrawer(activity.id)} /> ))} - */} + ); diff --git a/front/src/modules/companies/components/AddPersonToCompany.tsx b/front/src/modules/companies/components/AddPersonToCompany.tsx index d7bd68593..8a26ad3f1 100644 --- a/front/src/modules/companies/components/AddPersonToCompany.tsx +++ b/front/src/modules/companies/components/AddPersonToCompany.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useMutation, useQuery } from '@apollo/client'; +import { useMutation } from '@apollo/client'; import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; import { flip, offset, useFloating } from '@floating-ui/react'; @@ -8,11 +8,14 @@ import { v4 } from 'uuid'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { IconPlus } from '@/ui/display/icon'; import { LightIconButton } from '@/ui/input/button/components/LightIconButton'; +import { RelationPicker } from '@/ui/input/components/internal/relation-picker/components/RelationPicker'; +import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { DoubleTextInput } from '@/ui/object/field/meta-types/input/components/internal/DoubleTextInput'; import { FieldDoubleText } from '@/ui/object/field/types/FieldDoubleText'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; +import { FieldMetadataType } from '~/generated-metadata/graphql'; const StyledContainer = styled.div` position: static; @@ -65,43 +68,29 @@ export const AddPersonToCompany = ({ goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); - // TODO: refactor with useObjectMetadataItem V2 with typed hooks const { findManyQuery, updateOneMutation, createOneMutation } = useObjectMetadataItem({ objectNameSingular: 'person', }); - const { data: peopleNotInCompany } = useQuery(findManyQuery, { - variables: { - filter: { - companyId: { - neq: companyId, - }, - }, - }, - }); - const [updatePerson] = useMutation(updateOneMutation); const [createPerson] = useMutation(createOneMutation); - const handlePersonSelected = async ({ - selectedPersonId, - companyId, - }: { - selectedPersonId: string; - companyId: string | null; - }) => { - await updatePerson({ - variables: { - idToUpdate: selectedPersonId, - input: { - companyId: companyId, + const handlePersonSelected = + (companyId: string) => async (newPerson: EntityForSelect | null) => { + if (!newPerson) return; + await updatePerson({ + variables: { + idToUpdate: newPerson.id, + input: { + companyId: companyId, + }, }, - }, - refetchQueries: [getOperationName(findManyQuery) ?? ''], - }); - handleClosePicker(); - }; + refetchQueries: [getOperationName(findManyQuery) ?? ''], + }); + + handleClosePicker(); + }; const handleClosePicker = () => { if (isDropdownOpen) { @@ -111,21 +100,12 @@ export const AddPersonToCompany = ({ }; const handleOpenPicker = () => { - // TODO: TEMPORARY - example to implement when the picker is back - handleCreatePerson({ - firstValue: 'John', - secondValue: 'Doe', - }); - // handlePersonSelected({ - // companyId, - // selectedPersonId: peopleNotInCompany.people.edges[0].node.id, - // }); - // if (!isDropdownOpen) { - // setIsDropdownOpen(true); - // setHotkeyScopeAndMemorizePreviousScope( - // RelationPickerHotkeyScope.RelationPicker, - // ); - // } + if (!isDropdownOpen) { + setIsDropdownOpen(true); + setHotkeyScopeAndMemorizePreviousScope( + RelationPickerHotkeyScope.RelationPicker, + ); + } }; const handleCreatePerson = async ({ @@ -178,14 +158,23 @@ export const AddPersonToCompany = ({ /> ) : ( - <>todo - // setIsCreationDropdownOpen(true)} - // excludePersonIds={peopleIds} - // /> + )} )} diff --git a/front/src/modules/companies/components/CompanyChip.tsx b/front/src/modules/companies/components/CompanyChip.tsx index 60a8c5519..53312b758 100644 --- a/front/src/modules/companies/components/CompanyChip.tsx +++ b/front/src/modules/companies/components/CompanyChip.tsx @@ -18,7 +18,7 @@ export const CompanyChip = ({ }: CompanyChipProps) => ( theme.spacing(2)}; - margin-bottom: ${({ theme }) => theme.spacing(2)}; + margin-bottom: ${({ theme }) => theme.spacing(6)}; `; const StyledTitleContainer = styled.div` diff --git a/front/src/modules/companies/components/HooksCompanyBoardEffect.tsx b/front/src/modules/companies/components/HooksCompanyBoardEffect.tsx index 28daa6ca0..c49b15c35 100644 --- a/front/src/modules/companies/components/HooksCompanyBoardEffect.tsx +++ b/front/src/modules/companies/components/HooksCompanyBoardEffect.tsx @@ -9,8 +9,8 @@ import { PaginatedObjectTypeResults } from '@/object-record/types/PaginatedObjec import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns'; import { Opportunity } from '@/pipeline/types/Opportunity'; import { PipelineStep } from '@/pipeline/types/PipelineStep'; -import { turnFiltersIntoWhereClauseV2 } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClauseV2'; -import { turnSortsIntoOrderByV2 } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2'; +import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause'; +import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { useBoardActionBarEntries } from '@/ui/object/record-board/hooks/useBoardActionBarEntries'; import { useBoardContext } from '@/ui/object/record-board/hooks/useBoardContext'; import { useBoardContextMenuEntries } from '@/ui/object/record-board/hooks/useBoardContextMenuEntries'; @@ -88,12 +88,12 @@ export const HooksCompanyBoardEffect = () => { ), }); - const filter = turnFiltersIntoWhereClauseV2( + const filter = turnFiltersIntoWhereClause( mapViewFiltersToFilters(currentViewFilters), objectMetadataItem?.fields ?? [], ); - const orderBy = turnSortsIntoOrderByV2( + const orderBy = turnSortsIntoOrderBy( mapViewSortsToSorts(currentViewSorts), objectMetadataItem?.fields ?? [], ); diff --git a/front/src/modules/companies/components/NewCompanyProgressButton.tsx b/front/src/modules/companies/components/NewOpportunityButton.tsx similarity index 53% rename from front/src/modules/companies/components/NewCompanyProgressButton.tsx rename to front/src/modules/companies/components/NewOpportunityButton.tsx index 4441c1209..f287e86a8 100644 --- a/front/src/modules/companies/components/NewCompanyProgressButton.tsx +++ b/front/src/modules/companies/components/NewOpportunityButton.tsx @@ -1,6 +1,12 @@ import { useCallback, useContext, useState } from 'react'; +import { useQuery } from '@apollo/client'; +import { useCreateOpportunity } from '@/companies/hooks/useCreateOpportunity'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker'; +import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect'; import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { NewButton } from '@/ui/object/record-board/components/NewButton'; @@ -8,13 +14,14 @@ import { BoardColumnContext } from '@/ui/object/record-board/contexts/BoardColum import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; -export const NewCompanyProgressButton = () => { +export const NewOpportunityButton = () => { const [isCreatingCard, setIsCreatingCard] = useState(false); const column = useContext(BoardColumnContext); const pipelineStepId = column?.columnDefinition.id || ''; const { enqueueSnackBar } = useSnackBar(); + const { createOpportunity } = useCreateOpportunity(); const { goBackToPreviousHotkeyScope, @@ -33,7 +40,7 @@ export const NewCompanyProgressButton = () => { throw new Error('Pipeline stage id is not defined'); } - //createCompanyProgress(company.id, pipelineStepId); + createOpportunity(company.id, pipelineStepId); }; const handleNewClick = useCallback(() => { @@ -52,23 +59,38 @@ export const NewCompanyProgressButton = () => { relationPickerSearchFilterScopedState, ); - // const companies = useFilteredSearchCompanyQuery({ - // searchFilter: relationPickerSearchFilter, - // }); + const { findManyQuery } = useObjectMetadataItem({ + objectNameSingular: 'company', + }); + const useFindManyQuery = (options: any) => useQuery(findManyQuery, options); + const { identifiersMapper, searchQuery } = useRelationPicker(); + + const filteredSearchEntityResults = useFilteredSearchEntityQuery({ + queryHook: useFindManyQuery, + filters: [ + { + fieldNames: searchQuery?.computeFilterFields?.('company') ?? [], + filter: relationPickerSearchFilter, + }, + ], + orderByField: 'createdAt', + selectedIds: [], + mappingFunction: (record: any) => identifiersMapper?.(record, 'company'), + objectNamePlural: 'companies', + }); return ( <> {isCreatingCard ? ( - <>TODO + ) : ( - // )} diff --git a/front/src/modules/companies/components/CompanyProgressPicker.tsx b/front/src/modules/companies/components/OpportunityPicker.tsx similarity index 67% rename from front/src/modules/companies/components/CompanyProgressPicker.tsx rename to front/src/modules/companies/components/OpportunityPicker.tsx index e0d06e306..9fc235329 100644 --- a/front/src/modules/companies/components/CompanyProgressPicker.tsx +++ b/front/src/modules/companies/components/OpportunityPicker.tsx @@ -1,8 +1,13 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { useRecoilState } from 'recoil'; +import { useQuery } from '@apollo/client'; +import { useRecoilValue } from 'recoil'; -import { currentPipelineState } from '@/pipeline/states/currentPipelineState'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { currentPipelineStepsState } from '@/pipeline/states/currentPipelineStepsState'; +import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { IconChevronDown } from '@/ui/display/icon'; +import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker'; +import { SingleEntitySelectBase } from '@/ui/input/relation-picker/components/SingleEntitySelectBase'; import { useEntitySelectSearch } from '@/ui/input/relation-picker/hooks/useEntitySelectSearch'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; @@ -13,7 +18,7 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; -export type CompanyProgressPickerProps = { +export type OpportunityPickerProps = { companyId: string | null; onSubmit: ( newCompanyId: EntityForSelect | null, @@ -22,19 +27,33 @@ export type CompanyProgressPickerProps = { onCancel?: () => void; }; -export const CompanyProgressPicker = ({ - companyId, +export const OpportunityPicker = ({ onSubmit, onCancel, -}: CompanyProgressPickerProps) => { +}: OpportunityPickerProps) => { const containerRef = useRef(null); const { searchFilter, handleSearchFilterChange } = useEntitySelectSearch(); - // const companies = useFilteredSearchCompanyQuery({ - // searchFilter, - // selectedIds: companyId ? [companyId] : [], - // }); + const { findManyQuery } = useObjectMetadataItem({ + objectNameSingular: 'company', + }); + const useFindManyQuery = (options: any) => useQuery(findManyQuery, options); + const { identifiersMapper, searchQuery } = useRelationPicker(); + + const filteredSearchEntityResults = useFilteredSearchEntityQuery({ + queryHook: useFindManyQuery, + filters: [ + { + fieldNames: searchQuery?.computeFilterFields?.('company') ?? [], + filter: searchFilter, + }, + ], + orderByField: 'createdAt', + selectedIds: [], + mappingFunction: (record: any) => identifiersMapper?.(record, 'company'), + objectNamePlural: 'companies', + }); const [isProgressSelectionUnfolded, setIsProgressSelectionUnfolded] = useState(false); @@ -43,12 +62,7 @@ export const CompanyProgressPicker = ({ string | null >(null); - const [currentPipeline] = useRecoilState(currentPipelineState); - - const currentPipelineSteps = useMemo( - () => currentPipeline?.pipelineSteps ?? [], - [currentPipeline], - ); + const currentPipelineSteps = useRecoilValue(currentPipelineStepsState); const handlePipelineStepChange = (newPipelineStepId: string) => { setSelectedPipelineStepId(newPipelineStepId); @@ -110,13 +124,13 @@ export const CompanyProgressPicker = ({ /> - {/* */} + selectedEntity={filteredSearchEntityResults.selectedEntities[0]} + /> )} diff --git a/front/src/modules/companies/hooks/useCreateOpportunity.ts b/front/src/modules/companies/hooks/useCreateOpportunity.ts new file mode 100644 index 000000000..4bdf877a7 --- /dev/null +++ b/front/src/modules/companies/hooks/useCreateOpportunity.ts @@ -0,0 +1,34 @@ +import { useRecoilCallback } from 'recoil'; +import { v4 } from 'uuid'; + +import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord'; +import { Opportunity } from '@/pipeline/types/Opportunity'; +import { boardCardIdsByColumnIdFamilyState } from '@/ui/object/record-board/states/boardCardIdsByColumnIdFamilyState'; + +export const useCreateOpportunity = () => { + const { createOneObject: createOneOpportunity } = + useCreateOneObjectRecord({ + objectNameSingular: 'opportunity', + }); + + const createOpportunity = useRecoilCallback( + ({ set }) => + async (companyId: string, pipelineStepId: string) => { + const newUuid = v4(); + + set(boardCardIdsByColumnIdFamilyState(pipelineStepId), (oldValue) => [ + ...oldValue, + newUuid, + ]); + + await createOneOpportunity?.({ + id: newUuid, + pipelineStepId, + companyId: companyId, + }); + }, + [createOneOpportunity], + ); + + return { createOpportunity }; +}; diff --git a/front/src/modules/favorites/hooks/useFavorites.ts b/front/src/modules/favorites/hooks/useFavorites.ts index 9ba3d1193..492382434 100644 --- a/front/src/modules/favorites/hooks/useFavorites.ts +++ b/front/src/modules/favorites/hooks/useFavorites.ts @@ -172,7 +172,7 @@ export const useFavorites = ({ favorites.filter((favorite: Favorite) => favorite.id !== idToDelete), ); }, - [apolloClient, deleteOneMutation], + [apolloClient, deleteOneMutation, performOptimisticEvict], ); const computeNewPosition = (destIndex: number, sourceIndex: number) => { diff --git a/front/src/modules/object-metadata/hooks/useComputeDefinitionsFromFieldMetadata.ts b/front/src/modules/object-metadata/hooks/useComputeDefinitionsFromFieldMetadata.ts index 45f884009..f8cae40db 100644 --- a/front/src/modules/object-metadata/hooks/useComputeDefinitionsFromFieldMetadata.ts +++ b/front/src/modules/object-metadata/hooks/useComputeDefinitionsFromFieldMetadata.ts @@ -15,7 +15,9 @@ export const useComputeDefinitionsFromFieldMetadata = ( const activeFieldMetadataItems = useMemo( () => objectMetadataItem - ? objectMetadataItem.fields.filter(({ isActive }) => isActive) + ? objectMetadataItem.fields.filter( + ({ isActive, isSystem }) => isActive && !isSystem, + ) : [], [objectMetadataItem], ); diff --git a/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index 429066e6f..2d9951861 100644 --- a/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { useGenerateCreateOneObjectMutation } from '@/object-record/utils/generateCreateOneObjectMutation'; +import { useGenerateCacheFragment } from '@/object-record/utils/useGenerateCacheFragment'; import { useGenerateDeleteOneObjectMutation } from '@/object-record/utils/useGenerateDeleteOneObjectMutation'; import { useGenerateFindManyCustomObjectsQuery } from '@/object-record/utils/useGenerateFindManyCustomObjectsQuery'; import { useGenerateFindOneCustomObjectQuery } from '@/object-record/utils/useGenerateFindOneCustomObjectQuery'; @@ -36,6 +37,10 @@ export const useObjectMetadataItem = ({ const objectNotFoundInMetadata = !isDefined(objectMetadataItem); + const cacheFragment = useGenerateCacheFragment({ + objectMetadataItem, + }); + const findManyQuery = useGenerateFindManyCustomObjectsQuery({ objectMetadataItem, }); @@ -67,6 +72,7 @@ export const useObjectMetadataItem = ({ basePathToShowPage, objectMetadataItem, objectNotFoundInMetadata, + cacheFragment, findManyQuery, findOneQuery, createOneMutation, diff --git a/front/src/modules/object-record/components/RecordShowPage.tsx b/front/src/modules/object-record/components/RecordShowPage.tsx index 53e1f95e5..69ce641e5 100644 --- a/front/src/modules/object-record/components/RecordShowPage.tsx +++ b/front/src/modules/object-record/components/RecordShowPage.tsx @@ -8,6 +8,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { filterAvailableFieldMetadataItem } from '@/object-record/utils/filterAvailableFieldMetadataItem'; import { IconBuildingSkyscraper } from '@/ui/display/icon'; +import { useRelationPicker } from '@/ui/input/components/internal/relation-picker/hooks/useRelationPicker'; import { PageBody } from '@/ui/layout/page/PageBody'; import { PageContainer } from '@/ui/layout/page/PageContainer'; import { PageFavoriteButton } from '@/ui/layout/page/PageFavoriteButton'; @@ -40,6 +41,8 @@ export const RecordShowPage = () => { objectNameSingular, }); + const { identifiersMapper } = useRelationPicker(); + const { favorites, createFavorite, deleteFavorite } = useFavorites({ objectNamePlural: objectMetadataItem?.namePlural, }); @@ -118,6 +121,11 @@ export const RecordShowPage = () => { ? object.name.firstName + ' ' + object.name.lastName : object.name; + const recordIdentifiers = identifiersMapper?.( + object, + objectMetadataItem?.nameSingular ?? '', + ); + return ( @@ -144,8 +152,8 @@ export const RecordShowPage = () => { <>} avatarType="squared" diff --git a/front/src/modules/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition.ts b/front/src/modules/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition.ts index 9f015d3d8..926a23f20 100644 --- a/front/src/modules/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition.ts +++ b/front/src/modules/object-record/graphql/optimistic-effect-definition/getRecordOptimisticEffectDefinition.ts @@ -34,6 +34,7 @@ export const getRecordOptimisticEffectDefinition = ({ }, }; } + draft.edges.unshift({ node: newData, cursor: '' }); }); diff --git a/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts b/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts index 8de3e0bf1..2b9f495be 100644 --- a/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts +++ b/front/src/modules/object-record/hooks/useFindManyObjectRecords.ts @@ -30,24 +30,29 @@ export const useFindManyObjectRecords = < objectNamePlural, filter, orderBy, + limit, onCompleted, skip, }: Pick & { filter?: any; orderBy?: any; + limit?: number; onCompleted?: (data: PaginatedObjectTypeResults) => void; skip?: boolean; }) => { + const findManyQueryStateIdentifier = + objectNamePlural + JSON.stringify(filter); + const [lastCursor, setLastCursor] = useRecoilState( - cursorFamilyState(objectNamePlural), + cursorFamilyState(findManyQueryStateIdentifier), ); const [hasNextPage, setHasNextPage] = useRecoilState( - hasNextPageFamilyState(objectNamePlural), + hasNextPageFamilyState(findManyQueryStateIdentifier), ); const [, setIsFetchingMoreObjects] = useRecoilState( - isFetchingMoreObjectsFamilyState(objectNamePlural), + isFetchingMoreObjectsFamilyState(findManyQueryStateIdentifier), ); const { objectMetadataItem, objectNotFoundInMetadata, findManyQuery } = @@ -68,6 +73,7 @@ export const useFindManyObjectRecords = < variables: { filter: filter ?? {}, orderBy: orderBy ?? {}, + limit: limit ?? 30, }, onCompleted: (data) => { if (objectMetadataItem) { diff --git a/front/src/modules/object-record/hooks/useObjectRecordTable.ts b/front/src/modules/object-record/hooks/useObjectRecordTable.ts index c056f68e4..978a6bb49 100644 --- a/front/src/modules/object-record/hooks/useObjectRecordTable.ts +++ b/front/src/modules/object-record/hooks/useObjectRecordTable.ts @@ -2,8 +2,8 @@ import { useRecoilValue } from 'recoil'; import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { turnFiltersIntoWhereClauseV2 } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClauseV2'; -import { turnSortsIntoOrderByV2 } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2'; +import { turnFiltersIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFiltersIntoWhereClause'; +import { turnSortsIntoOrderBy } from '@/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy'; import { useRecordTableScopedStates } from '@/ui/object/record-table/hooks/internal/useRecordTableScopedStates'; import { useRecordTable } from '@/ui/object/record-table/hooks/useRecordTable'; @@ -31,11 +31,11 @@ export const useObjectRecordTable = () => { const tableFilters = useRecoilValue(tableFiltersState); const tableSorts = useRecoilValue(tableSortsState); - const filter = turnFiltersIntoWhereClauseV2( + const filter = turnFiltersIntoWhereClause( tableFilters, foundObjectMetadataItem?.fields ?? [], ); - const orderBy = turnSortsIntoOrderByV2( + const orderBy = turnSortsIntoOrderBy( tableSorts, foundObjectMetadataItem?.fields ?? [], ); diff --git a/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts b/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts index 91c25e99d..cc20dbee8 100644 --- a/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts +++ b/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@apollo/client'; +import { useApolloClient, useMutation } from '@apollo/client'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; @@ -11,10 +11,13 @@ export const useUpdateOneObjectRecord = ({ objectMetadataItem: foundObjectMetadataItem, objectNotFoundInMetadata, updateOneMutation, + cacheFragment, } = useObjectMetadataItem({ objectNameSingular, }); + const { cache } = useApolloClient(); + // TODO: type this with a minimal type at least with Record const [mutate] = useMutation(updateOneMutation); @@ -29,6 +32,15 @@ export const useUpdateOneObjectRecord = ({ return null; } + const cachedRecordId = cache.identify({ + __typename: capitalize(foundObjectMetadataItem?.nameSingular ?? ''), + id: idToUpdate, + }); + const cachedRecord = cache.readFragment({ + id: cachedRecordId, + fragment: cacheFragment, + }); + const updatedObject = await mutate({ variables: { idToUpdate: idToUpdate, @@ -38,7 +50,7 @@ export const useUpdateOneObjectRecord = ({ }, optimisticResponse: { [`update${capitalize(objectNameSingular)}`]: { - id: idToUpdate, + ...(cachedRecord ?? {}), ...input, }, }, diff --git a/front/src/modules/object-record/utils/useGenerateCacheFragment.ts b/front/src/modules/object-record/utils/useGenerateCacheFragment.ts new file mode 100644 index 000000000..3f88fe78c --- /dev/null +++ b/front/src/modules/object-record/utils/useGenerateCacheFragment.ts @@ -0,0 +1,29 @@ +import { gql } from '@apollo/client'; + +import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery'; +import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { capitalize } from '~/utils/string/capitalize'; + +export const useGenerateCacheFragment = ({ + objectMetadataItem, +}: { + objectMetadataItem: ObjectMetadataItem | undefined | null; +}) => { + const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery(); + + if (!objectMetadataItem) { + return EMPTY_MUTATION; + } + + const capitalizedObjectName = capitalize(objectMetadataItem.nameSingular); + + return gql` + fragment ${capitalizedObjectName}Fragment on ${capitalizedObjectName} { + id + ${objectMetadataItem.fields + .map((field) => mapFieldMetadataToGraphQLQuery(field)) + .join('\n')} + } +`; +}; diff --git a/front/src/modules/pipeline/components/PipelineAddButton.tsx b/front/src/modules/pipeline/components/PipelineAddButton.tsx index 4a0697ece..db310b101 100644 --- a/front/src/modules/pipeline/components/PipelineAddButton.tsx +++ b/front/src/modules/pipeline/components/PipelineAddButton.tsx @@ -1,4 +1,5 @@ -import { CompanyProgressPicker } from '@/companies/components/CompanyProgressPicker'; +import { OpportunityPicker } from '@/companies/components/OpportunityPicker'; +import { useCreateOpportunity } from '@/companies/hooks/useCreateOpportunity'; import { PageHotkeyScope } from '@/types/PageHotkeyScope'; import { IconPlus } from '@/ui/display/icon/index'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; @@ -17,6 +18,8 @@ export const PipelineAddButton = () => { dropdownScopeId: 'add-pipeline-progress', }); + const { createOpportunity } = useCreateOpportunity(); + const handleCompanySelected = ( selectedCompany: EntityForSelect | null, selectedPipelineStepId: string | null, @@ -24,9 +27,7 @@ export const PipelineAddButton = () => { if (!selectedCompany?.id) { enqueueSnackBar( 'There was a problem with the company selection, please retry.', - { - variant: 'error', - }, + { variant: 'error' }, ); logError('There was a problem with the company selection, please retry.'); @@ -36,16 +37,14 @@ export const PipelineAddButton = () => { if (!selectedPipelineStepId) { enqueueSnackBar( 'There was a problem with the pipeline stage selection, please retry.', - { - variant: 'error', - }, + { variant: 'error' }, ); - logError('There was a problem with the pipeline stage selection.'); + logError('There was a problem with the pipeline step selection.'); return; } closeDropdown(); - //createCompanyProgress(selectedCompany.id, selectedPipelineStepId); + createOpportunity(selectedCompany.id, selectedPipelineStepId); }; return ( @@ -62,7 +61,7 @@ export const PipelineAddButton = () => { /> } dropdownComponents={ - void; onCancel?: () => void; width?: number; + excludeRecordIds?: string[]; initialSearchFilter?: string | null; fieldDefinition: FieldDefinition; }; @@ -25,6 +26,7 @@ export const RelationPicker = ({ recordId, onSubmit, onCancel, + excludeRecordIds, width, initialSearchFilter, fieldDefinition, @@ -63,6 +65,7 @@ export const RelationPicker = ({ fieldDefinition.metadata.relationObjectMetadataNameSingular, ), selectedIds: recordId ? [recordId] : [], + excludeEntityIds: excludeRecordIds, objectNamePlural: fieldDefinition.metadata.relationObjectMetadataNamePlural, }); @@ -72,8 +75,8 @@ export const RelationPicker = ({ return ( (useIsMobile() ? theme.spacing(3) : '0')}; - height: ${() => (useIsMobile() ? '100%' : 'auto')}; + height: ${() => (useIsMobile() ? '100%' : '100%')}; overflow-x: ${() => (useIsMobile() ? 'hidden' : 'auto')}; width: 100%; `; diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx index 1cd972243..8fe92a5bd 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageLeftContainer.tsx @@ -31,6 +31,12 @@ const StyledInnerContainer = styled.div` }}; `; +const StyledIntermediateContainer = styled.div` + display: flex; + flex-direction: column; + padding-bottom: ${({ theme }) => theme.spacing(3)}; +`; + export type ShowPageLeftContainerProps = { children: ReactElement[]; }; @@ -46,7 +52,9 @@ export const ShowPageLeftContainer = ({ ) : ( - {children} + + {children} + ); diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx index 741f8eb1b..8d25c3740 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx @@ -91,16 +91,18 @@ export const ShowPageSummaryCard = ({ const onFileChange = (e: ChangeEvent) => { if (e.target.files) onUploadPicture?.(e.target.files[0]); }; - const handleAvatarClick = () => { - inputFileRef?.current?.click?.(); - }; + + // Todo - add back in when we have the ability to upload a picture + // const handleAvatarClick = () => { + // inputFileRef?.current?.click?.(); + // }; return ( & { }; }; -export const turnFiltersIntoWhereClauseV2 = ( +export const turnFiltersIntoWhereClause = ( filters: FilterToTurnIntoWhereClause[], fields: Pick[], ) => { diff --git a/front/src/modules/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2.ts b/front/src/modules/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts similarity index 95% rename from front/src/modules/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2.ts rename to front/src/modules/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts index 1cd5a1c90..d0f18be39 100644 --- a/front/src/modules/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderByV2.ts +++ b/front/src/modules/ui/object/object-sort-dropdown/utils/turnSortsIntoOrderBy.ts @@ -2,7 +2,7 @@ import { Field } from '~/generated/graphql'; import { Sort } from '../types/Sort'; -export const turnSortsIntoOrderByV2 = ( +export const turnSortsIntoOrderBy = ( sorts: Sort[], fields: Pick[], ) => { diff --git a/front/src/modules/ui/object/record-board/types/BoardOptions.ts b/front/src/modules/ui/object/record-board/types/BoardOptions.ts index fe137f70b..733909477 100644 --- a/front/src/modules/ui/object/record-board/types/BoardOptions.ts +++ b/front/src/modules/ui/object/record-board/types/BoardOptions.ts @@ -1,12 +1,6 @@ import { ComponentType } from 'react'; -import { Opportunity } from '@/pipeline/types/Opportunity'; -import { FilterDefinitionByEntity } from '@/ui/object/object-filter-dropdown/types/FilterDefinitionByEntity'; -import { SortDefinition } from '@/ui/object/object-sort-dropdown/types/SortDefinition'; - export type BoardOptions = { newCardComponent: React.ReactNode; CardComponent: ComponentType; - filterDefinitions: FilterDefinitionByEntity[]; - sortDefinitions: SortDefinition[]; }; diff --git a/front/src/modules/users/components/FilterDropdownUserSearchSelect.tsx b/front/src/modules/users/components/FilterDropdownUserSearchSelect.tsx deleted file mode 100644 index 0980c5f84..000000000 --- a/front/src/modules/users/components/FilterDropdownUserSearchSelect.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useQuery } from '@apollo/client'; - -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; -import { ObjectFilterDropdownEntitySearchSelect } from '@/ui/object/object-filter-dropdown/components/ObjectFilterDropdownEntitySearchSelect'; -import { useFilter } from '@/ui/object/object-filter-dropdown/hooks/useFilter'; - -export const FilterDropdownUserSearchSelect = () => { - const { - objectFilterDropdownSearchInput, - objectFilterDropdownSelectedEntityId, - } = useFilter(); - - const { findManyQuery } = useObjectMetadataItem({ - objectNameSingular: 'workspaceMember', - }); - - const useFindManyWorkspaceMembers = (options: any) => - useQuery(findManyQuery, options); - - const workspaceMembers = useFilteredSearchEntityQuery({ - queryHook: useFindManyWorkspaceMembers, - filters: [ - { - fieldNames: ['name.firstName', 'name.lastName'], - filter: objectFilterDropdownSearchInput, - }, - ], - orderByField: 'createdAt', - mappingFunction: (workspaceMember) => ({ - entityType: 'WorkspaceMember', - id: workspaceMember.id, - name: - workspaceMember.name.firstName + ' ' + workspaceMember.name.lastName, - avatarType: 'rounded', - avatarUrl: '', - record: workspaceMember, - }), - selectedIds: objectFilterDropdownSelectedEntityId - ? [objectFilterDropdownSelectedEntityId] - : [], - objectNamePlural: 'workspaceMembers', - }); - - return ( - - ); -}; diff --git a/front/src/modules/views/types/ViewField.ts b/front/src/modules/views/types/ViewField.ts index 2ed6961eb..f13bb804f 100644 --- a/front/src/modules/views/types/ViewField.ts +++ b/front/src/modules/views/types/ViewField.ts @@ -1,5 +1,5 @@ -import { BoardFieldDefinition } from '@/ui/object/record-board/types/BoardFieldDefinition'; import { FieldMetadata } from '@/ui/object/field/types/FieldMetadata'; +import { BoardFieldDefinition } from '@/ui/object/record-board/types/BoardFieldDefinition'; import { ColumnDefinition } from '@/ui/object/record-table/types/ColumnDefinition'; export type ViewField = { diff --git a/front/src/pages/opportunities/opportunitiesBoardOptions.tsx b/front/src/pages/opportunities/opportunitiesBoardOptions.tsx index 8e80d42b9..9457b8c58 100644 --- a/front/src/pages/opportunities/opportunitiesBoardOptions.tsx +++ b/front/src/pages/opportunities/opportunitiesBoardOptions.tsx @@ -1,13 +1,8 @@ import { CompanyBoardCard } from '@/companies/components/CompanyBoardCard'; -import { NewCompanyProgressButton } from '@/companies/components/NewCompanyProgressButton'; +import { NewOpportunityButton } from '@/companies/components/NewOpportunityButton'; import { BoardOptions } from '@/ui/object/record-board/types/BoardOptions'; -import { opportunityBoardFilterDefinitions } from './constants/opportunityBoardFilterDefinitions'; -import { opportunityBoardSortDefinitions } from './constants/opportunityBoardSortDefinitions'; - export const opportunitiesBoardOptions: BoardOptions = { - newCardComponent: , + newCardComponent: , CardComponent: CompanyBoardCard, - filterDefinitions: opportunityBoardFilterDefinitions, - sortDefinitions: opportunityBoardSortDefinitions, }; diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts index 3904b4bfd..74c203fb3 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/company.ts @@ -89,7 +89,7 @@ export const seedCompanyFieldMetadata = async ( description: undefined, icon: 'IconCalendar', isNullable: false, - isSystem: true, + isSystem: false, defaultValue: { type: 'now' }, }, { diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/opportunity.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/opportunity.ts index fff9d377c..32bf63f57 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/opportunity.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/opportunity.ts @@ -84,7 +84,7 @@ export const seedOpportunityFieldMetadata = async ( description: undefined, icon: 'IconCalendar', isNullable: false, - isSystem: true, + isSystem: false, defaultValue: { type: 'now' }, }, { @@ -175,7 +175,7 @@ export const seedOpportunityFieldMetadata = async ( description: 'Opportunity pipeline step', icon: 'IconKanban', isNullable: true, - isSystem: false, + isSystem: true, defaultValue: undefined, }, { @@ -239,7 +239,7 @@ export const seedOpportunityFieldMetadata = async ( description: 'Opportunity person', icon: 'IconUser', isNullable: true, - isSystem: false, + isSystem: true, defaultValue: undefined, }, { diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts index 58f8b62a4..9d81e0362 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts @@ -89,7 +89,7 @@ export const seedPersonFieldMetadata = async ( description: undefined, icon: 'IconCalendar', isNullable: false, - isSystem: true, + isSystem: false, defaultValue: { type: 'now' }, }, { @@ -126,7 +126,7 @@ export const seedPersonFieldMetadata = async ( }, description: 'Contact’s name', icon: 'IconUser', - isNullable: false, + isNullable: true, isSystem: false, defaultValue: { firstName: '', lastName: '' }, }, @@ -301,11 +301,11 @@ export const seedPersonFieldMetadata = async ( workspaceId: SeedWorkspaceId, isActive: true, type: FieldMetadataType.RELATION, - name: 'pointOfContactForOpporunities', + name: 'pointOfContactForOpportunities', label: 'POC for Opportunities', targetColumnMap: {}, description: 'Point of Contact for Opportuniites', - icon: 'IconArrowTarget', + icon: 'IconTargetArrow', isNullable: true, isSystem: false, defaultValue: undefined, diff --git a/server/src/database/typeorm/core/migrations/1700661180856-addCascadeDeleteOnRefreshTokenUser.ts b/server/src/database/typeorm/core/migrations/1700661180856-addCascadeDeleteOnRefreshTokenUser.ts index cebae100c..84df624d0 100644 --- a/server/src/database/typeorm/core/migrations/1700661180856-addCascadeDeleteOnRefreshTokenUser.ts +++ b/server/src/database/typeorm/core/migrations/1700661180856-addCascadeDeleteOnRefreshTokenUser.ts @@ -1,16 +1,25 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; -export class AddCascadeDeleteOnRefreshTokenUser1700661180856 implements MigrationInterface { - name = 'AddCascadeDeleteOnRefreshTokenUser1700661180856' +export class AddCascadeDeleteOnRefreshTokenUser1700661180856 + implements MigrationInterface +{ + name = 'AddCascadeDeleteOnRefreshTokenUser1700661180856'; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "core"."refreshToken" DROP CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e"`); - await queryRunner.query(`ALTER TABLE "core"."refreshToken" ADD CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e" FOREIGN KEY ("userId") REFERENCES "core"."user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "core"."refreshToken" DROP CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e"`); - await queryRunner.query(`ALTER TABLE "core"."refreshToken" ADD CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e" FOREIGN KEY ("userId") REFERENCES "core"."user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."refreshToken" DROP CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."refreshToken" ADD CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e" FOREIGN KEY ("userId") REFERENCES "core"."user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."refreshToken" DROP CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."refreshToken" ADD CONSTRAINT "FK_7008a2b0fb083127f60b5f4448e" FOREIGN KEY ("userId") REFERENCES "core"."user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } } diff --git a/server/src/database/typeorm/core/migrations/1700663611659-addWorkspaceDeleteCascadeSetNullInUser.ts b/server/src/database/typeorm/core/migrations/1700663611659-addWorkspaceDeleteCascadeSetNullInUser.ts index f1f7ddece..bd48bc5ef 100644 --- a/server/src/database/typeorm/core/migrations/1700663611659-addWorkspaceDeleteCascadeSetNullInUser.ts +++ b/server/src/database/typeorm/core/migrations/1700663611659-addWorkspaceDeleteCascadeSetNullInUser.ts @@ -1,16 +1,25 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; -export class AddWorkspaceDeleteCascadeSetNullInUser1700663611659 implements MigrationInterface { - name = 'AddWorkspaceDeleteCascadeSetNullInUser1700663611659' +export class AddWorkspaceDeleteCascadeSetNullInUser1700663611659 + implements MigrationInterface +{ + name = 'AddWorkspaceDeleteCascadeSetNullInUser1700663611659'; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "core"."user" DROP CONSTRAINT "FK_2ec910029395fa7655621c88908"`); - await queryRunner.query(`ALTER TABLE "core"."user" ADD CONSTRAINT "FK_2ec910029395fa7655621c88908" FOREIGN KEY ("defaultWorkspaceId") REFERENCES "core"."workspace"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "core"."user" DROP CONSTRAINT "FK_2ec910029395fa7655621c88908"`); - await queryRunner.query(`ALTER TABLE "core"."user" ADD CONSTRAINT "FK_2ec910029395fa7655621c88908" FOREIGN KEY ("defaultWorkspaceId") REFERENCES "core"."workspace"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."user" DROP CONSTRAINT "FK_2ec910029395fa7655621c88908"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."user" ADD CONSTRAINT "FK_2ec910029395fa7655621c88908" FOREIGN KEY ("defaultWorkspaceId") REFERENCES "core"."workspace"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."user" DROP CONSTRAINT "FK_2ec910029395fa7655621c88908"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."user" ADD CONSTRAINT "FK_2ec910029395fa7655621c88908" FOREIGN KEY ("defaultWorkspaceId") REFERENCES "core"."workspace"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } } diff --git a/server/src/database/typeorm/metadata/migrations/1700650554672-addWorkspaceCacheVersion.ts b/server/src/database/typeorm/metadata/migrations/1700650554672-addWorkspaceCacheVersion.ts index 062fd1f4c..5615e635b 100644 --- a/server/src/database/typeorm/metadata/migrations/1700650554672-addWorkspaceCacheVersion.ts +++ b/server/src/database/typeorm/metadata/migrations/1700650554672-addWorkspaceCacheVersion.ts @@ -1,14 +1,17 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; -export class AddWorkspaceCacheVersion1700650554672 implements MigrationInterface { - name = 'AddWorkspaceCacheVersion1700650554672' +export class AddWorkspaceCacheVersion1700650554672 + implements MigrationInterface +{ + name = 'AddWorkspaceCacheVersion1700650554672'; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "metadata"."workspaceCacheVersion" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "workspaceId" character varying NOT NULL, "version" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_1a80ecf2638b477809403cc26ed" UNIQUE ("workspaceId"), CONSTRAINT "PK_5d502f8dbfb5b9a8bf2439320e9" PRIMARY KEY ("id"))`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE "metadata"."workspaceCacheVersion"`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "metadata"."workspaceCacheVersion" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "workspaceId" character varying NOT NULL, "version" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_1a80ecf2638b477809403cc26ed" UNIQUE ("workspaceId"), CONSTRAINT "PK_5d502f8dbfb5b9a8bf2439320e9" PRIMARY KEY ("id"))`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "metadata"."workspaceCacheVersion"`); + } } diff --git a/server/src/database/typeorm/metadata/migrations/1700661538754-addCascadeDeleteOnRelationObject.ts b/server/src/database/typeorm/metadata/migrations/1700661538754-addCascadeDeleteOnRelationObject.ts index 93d356ac3..19405755d 100644 --- a/server/src/database/typeorm/metadata/migrations/1700661538754-addCascadeDeleteOnRelationObject.ts +++ b/server/src/database/typeorm/metadata/migrations/1700661538754-addCascadeDeleteOnRelationObject.ts @@ -1,20 +1,37 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm'; -export class AddCascadeDeleteOnRelationObject1700661538754 implements MigrationInterface { - name = 'AddCascadeDeleteOnRelationObject1700661538754' +export class AddCascadeDeleteOnRelationObject1700661538754 + implements MigrationInterface +{ + name = 'AddCascadeDeleteOnRelationObject1700661538754'; - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824"`); - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d"`); - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d" FOREIGN KEY ("fromObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824" FOREIGN KEY ("toObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824"`); - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d"`); - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d" FOREIGN KEY ("fromObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824" FOREIGN KEY ("toObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d" FOREIGN KEY ("fromObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824" FOREIGN KEY ("toObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" DROP CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d"`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_f2a0acd3a548ee446a1a35df44d" FOREIGN KEY ("fromObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "metadata"."relationMetadata" ADD CONSTRAINT "FK_0f781f589e5a527b8f3d3a4b824" FOREIGN KEY ("toObjectMetadataId") REFERENCES "metadata"."objectMetadata"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + } } diff --git a/server/src/workspace/workspace-manager/standard-objects/opportunity.ts b/server/src/workspace/workspace-manager/standard-objects/opportunity.ts index 432fcf4a7..32bb2b33b 100644 --- a/server/src/workspace/workspace-manager/standard-objects/opportunity.ts +++ b/server/src/workspace/workspace-manager/standard-objects/opportunity.ts @@ -63,6 +63,7 @@ const opportunityMetadata = { }, description: 'Opportunity pipeline step', icon: 'IconKanban', + isSystem: true, isNullable: true, }, { @@ -90,6 +91,7 @@ const opportunityMetadata = { description: 'Opportunity person', icon: 'IconUser', isNullable: true, + isSystem: true, }, { isCustom: false, diff --git a/server/src/workspace/workspace-manager/standard-objects/person.ts b/server/src/workspace/workspace-manager/standard-objects/person.ts index 3e874fcbf..c6376c0f1 100644 --- a/server/src/workspace/workspace-manager/standard-objects/person.ts +++ b/server/src/workspace/workspace-manager/standard-objects/person.ts @@ -23,7 +23,7 @@ const personMetadata = { }, description: 'Contact’s name', icon: 'IconUser', - isNullable: false, + isNullable: true, }, { isCustom: false, @@ -116,7 +116,7 @@ const personMetadata = { }, description: 'Contact’s avatar', icon: 'IconFileUpload', - isNullable: false, + isNullable: true, }, // Relations { @@ -151,8 +151,8 @@ const personMetadata = { label: 'POC for Opportunities', targetColumnMap: {}, description: 'Point of Contact for Opportunities', - icon: 'IconArrowTarget', - isNullable: false, + icon: 'IconTargetArrow', + isNullable: true, }, { isCustom: false, diff --git a/server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts b/server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts index 2fc766488..e0064f189 100644 --- a/server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts +++ b/server/src/workspace/workspace-manager/standard-objects/standard-object-metadata.ts @@ -47,7 +47,7 @@ export const basicFieldsMetadata: Partial[] = [ value: 'id', }, isNullable: true, - // isSystem: true, + isSystem: true, isCustom: false, isActive: true, defaultValue: { type: 'uuid' }, @@ -75,6 +75,7 @@ export const basicFieldsMetadata: Partial[] = [ icon: 'IconCalendar', isNullable: true, isCustom: false, + isSystem: true, isActive: true, defaultValue: { type: 'now' }, },