Feat/generic editable board card (#1089)

* Fixed BoardColumnMenu

* Fixed naming

* Optimized board loading

* Added GenericEditableField

* Introduce GenericEditableField for BoardCards

* remove logs

* delete unused files

* fix stories

---------

Co-authored-by: corentin <corentin@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-08-09 05:08:37 +02:00
committed by GitHub
parent 77d356f78a
commit 3666980ccc
103 changed files with 1551 additions and 922 deletions

View File

@ -23,6 +23,7 @@
"apollo-upload-client": "^17.0.0", "apollo-upload-client": "^17.0.0",
"cmdk": "^0.2.0", "cmdk": "^0.2.0",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"deep-equal": "^2.2.2",
"framer-motion": "^10.12.17", "framer-motion": "^10.12.17",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"hex-rgb": "^5.0.0", "hex-rgb": "^5.0.0",
@ -124,6 +125,7 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@types/apollo-upload-client": "^17.0.2", "@types/apollo-upload-client": "^17.0.2",
"@types/deep-equal": "^1.0.1",
"@types/jest": "^27.5.2", "@types/jest": "^27.5.2",
"@types/js-cookie": "^3.0.3", "@types/js-cookie": "^3.0.3",
"@types/lodash.debounce": "^4.0.7", "@types/lodash.debounce": "^4.0.7",

View File

@ -1,4 +1,4 @@
import { useLocation } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
@ -17,11 +17,15 @@ import MainNavbar from '@/ui/navbar/components/MainNavbar';
import NavItem from '@/ui/navbar/components/NavItem'; import NavItem from '@/ui/navbar/components/NavItem';
import NavTitle from '@/ui/navbar/components/NavTitle'; import NavTitle from '@/ui/navbar/components/NavTitle';
import { measureTotalFrameLoad } from './utils/measureTotalFrameLoad';
export function AppNavbar() { export function AppNavbar() {
const theme = useTheme(); const theme = useTheme();
const currentPath = useLocation().pathname; const currentPath = useLocation().pathname;
const { openCommandMenu } = useCommandMenu(); const { openCommandMenu } = useCommandMenu();
const navigate = useNavigate();
const isInSubMenu = useIsSubMenuNavbarDisplayed(); const isInSubMenu = useIsSubMenuNavbarDisplayed();
return ( return (
@ -62,12 +66,22 @@ export function AppNavbar() {
<NavItem <NavItem
label="People" label="People"
to="/people" to="/people"
onClick={() => {
measureTotalFrameLoad('people');
navigate('/people');
}}
icon={<IconUser size={theme.icon.size.md} />} icon={<IconUser size={theme.icon.size.md} />}
active={currentPath === '/people'} active={currentPath === '/people'}
/> />
<NavItem <NavItem
label="Opportunities" label="Opportunities"
to="/opportunities" // to="/opportunities"
onClick={() => {
measureTotalFrameLoad('opportunities');
navigate('/opportunities');
}}
icon={<IconTargetArrow size={theme.icon.size.md} />} icon={<IconTargetArrow size={theme.icon.size.md} />}
active={currentPath === '/opportunities'} active={currentPath === '/opportunities'}
/> />

View File

@ -1942,7 +1942,7 @@ export type User = {
phoneNumber?: Maybe<Scalars['String']>; phoneNumber?: Maybe<Scalars['String']>;
settings: UserSettings; settings: UserSettings;
settingsId: Scalars['String']; settingsId: Scalars['String'];
supportUserHash: Scalars['String']; supportUserHash?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime']; updatedAt: Scalars['DateTime'];
workspaceMember?: Maybe<WorkspaceMember>; workspaceMember?: Maybe<WorkspaceMember>;
}; };
@ -2437,7 +2437,7 @@ export type VerifyMutationVariables = Exact<{
}>; }>;
export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash: string, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type RenewTokenMutationVariables = Exact<{ export type RenewTokenMutationVariables = Exact<{
refreshToken: Scalars['String']; refreshToken: Scalars['String'];
@ -2451,7 +2451,7 @@ export type ImpersonateMutationVariables = Exact<{
}>; }>;
export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash: string, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, colorScheme: ColorScheme, locale: string } }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } };
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
@ -2613,15 +2613,12 @@ export type UpdatePipelineStageMutationVariables = Exact<{
export type UpdatePipelineStageMutation = { __typename?: 'Mutation', updateOnePipelineStage?: { __typename?: 'PipelineStage', id: string, name: string, color: string } | null }; export type UpdatePipelineStageMutation = { __typename?: 'Mutation', updateOnePipelineStage?: { __typename?: 'PipelineStage', id: string, name: string, color: string } | null };
export type UpdateOnePipelineProgressMutationVariables = Exact<{ export type UpdateOnePipelineProgressMutationVariables = Exact<{
id?: InputMaybe<Scalars['String']>; data: PipelineProgressUpdateInput;
amount?: InputMaybe<Scalars['Int']>; where: PipelineProgressWhereUniqueInput;
closeDate?: InputMaybe<Scalars['DateTime']>;
probability?: InputMaybe<Scalars['Int']>;
pointOfContactId?: InputMaybe<Scalars['String']>;
}>; }>;
export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string, amount?: number | null, closeDate?: string | null } | null }; export type UpdateOnePipelineProgressMutation = { __typename?: 'Mutation', updateOnePipelineProgress?: { __typename?: 'PipelineProgress', id: string, amount?: number | null, closeDate?: string | null, probability?: number | null } | null };
export type UpdateOnePipelineProgressStageMutationVariables = Exact<{ export type UpdateOnePipelineProgressStageMutationVariables = Exact<{
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -2685,7 +2682,7 @@ export type SearchActivityQuery = { __typename?: 'Query', searchResults: Array<{
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null, canImpersonate: boolean, supportUserHash: string, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } }; export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, allowImpersonation: boolean, workspace: { __typename?: 'Workspace', id: string, domainName?: string | null, displayName?: string | null, logo?: string | null, inviteHash?: string | null } } | null, settings: { __typename?: 'UserSettings', id: string, locale: string, colorScheme: ColorScheme } } };
export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>; export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>;
@ -4564,14 +4561,12 @@ export type UpdatePipelineStageMutationHookResult = ReturnType<typeof useUpdateP
export type UpdatePipelineStageMutationResult = Apollo.MutationResult<UpdatePipelineStageMutation>; export type UpdatePipelineStageMutationResult = Apollo.MutationResult<UpdatePipelineStageMutation>;
export type UpdatePipelineStageMutationOptions = Apollo.BaseMutationOptions<UpdatePipelineStageMutation, UpdatePipelineStageMutationVariables>; export type UpdatePipelineStageMutationOptions = Apollo.BaseMutationOptions<UpdatePipelineStageMutation, UpdatePipelineStageMutationVariables>;
export const UpdateOnePipelineProgressDocument = gql` export const UpdateOnePipelineProgressDocument = gql`
mutation UpdateOnePipelineProgress($id: String, $amount: Int, $closeDate: DateTime, $probability: Int, $pointOfContactId: String) { mutation UpdateOnePipelineProgress($data: PipelineProgressUpdateInput!, $where: PipelineProgressWhereUniqueInput!) {
updateOnePipelineProgress( updateOnePipelineProgress(where: $where, data: $data) {
where: {id: $id}
data: {amount: $amount, closeDate: $closeDate, probability: $probability, pointOfContact: {connect: {id: $pointOfContactId}}}
) {
id id
amount amount
closeDate closeDate
probability
} }
} }
`; `;
@ -4590,11 +4585,8 @@ export type UpdateOnePipelineProgressMutationFn = Apollo.MutationFunction<Update
* @example * @example
* const [updateOnePipelineProgressMutation, { data, loading, error }] = useUpdateOnePipelineProgressMutation({ * const [updateOnePipelineProgressMutation, { data, loading, error }] = useUpdateOnePipelineProgressMutation({
* variables: { * variables: {
* id: // value for 'id' * data: // value for 'data'
* amount: // value for 'amount' * where: // value for 'where'
* closeDate: // value for 'closeDate'
* probability: // value for 'probability'
* pointOfContactId: // value for 'pointOfContactId'
* }, * },
* }); * });
*/ */

View File

@ -17,10 +17,7 @@ const meta: Meta<typeof EntityBoard> = {
decorators: [ decorators: [
(Story) => ( (Story) => (
<RecoilScope SpecificContext={CompanyBoardContext}> <RecoilScope SpecificContext={CompanyBoardContext}>
<HooksCompanyBoard <HooksCompanyBoard orderBy={defaultPipelineProgressOrderBy} />
availableFilters={[]}
orderBy={defaultPipelineProgressOrderBy}
/>
<MemoryRouter> <MemoryRouter>
<Story /> <Story />
</MemoryRouter> </MemoryRouter>

View File

@ -19,10 +19,7 @@ const meta: Meta<typeof CompanyBoardCard> = {
decorators: [ decorators: [
(Story) => ( (Story) => (
<RecoilScope SpecificContext={CompanyBoardContext}> <RecoilScope SpecificContext={CompanyBoardContext}>
<HooksCompanyBoard <HooksCompanyBoard orderBy={defaultPipelineProgressOrderBy} />
availableFilters={[]}
orderBy={defaultPipelineProgressOrderBy}
/>
<RecoilScope SpecificContext={BoardColumnContext}> <RecoilScope SpecificContext={BoardColumnContext}>
<BoardCardIdContext.Provider value={mockedPipelineProgressData[1].id}> <BoardCardIdContext.Provider value={mockedPipelineProgressData[1].id}>
<MemoryRouter> <MemoryRouter>

View File

@ -1,27 +1,21 @@
import { ReactNode, useCallback, useContext } from 'react'; import { ReactNode, useContext } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
import { PipelineProgressPointOfContactEditableField } from '@/pipeline/editable-field/components/PipelineProgressPointOfContactEditableField';
import { ProbabilityEditableField } from '@/pipeline/editable-field/components/ProbabilityEditableField';
import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '@/pipeline/queries';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState';
import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState'; import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState';
import { EntityChipVariant } from '@/ui/chip/components/EntityChip'; import { EntityChipVariant } from '@/ui/chip/components/EntityChip';
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField'; import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField';
import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField';
import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon';
import { IconCalendarEvent } from '@/ui/icon';
import { import {
Checkbox, Checkbox,
CheckboxVariant, CheckboxVariant,
} from '@/ui/input/checkbox/components/Checkbox'; } from '@/ui/input/checkbox/components/Checkbox';
import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql'; import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
import { PipelineProgressForBoard } from '../types/CompanyProgress'; import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState';
import { CompanyChip } from './CompanyChip'; import { CompanyChip } from './CompanyChip';
@ -106,8 +100,6 @@ const StyledFieldContainer = styled.div`
`; `;
export function CompanyBoardCard() { export function CompanyBoardCard() {
const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation();
const boardCardId = useContext(BoardCardIdContext); const boardCardId = useContext(BoardCardIdContext);
const [companyProgress] = useRecoilState( const [companyProgress] = useRecoilState(
@ -118,6 +110,7 @@ export function CompanyBoardCard() {
const [selectedBoardCards, setSelectedBoardCards] = useRecoilState( const [selectedBoardCards, setSelectedBoardCards] = useRecoilState(
selectedBoardCardIdsState, selectedBoardCardIdsState,
); );
const fieldsDefinitions = useRecoilValue(fieldsDefinitionsState);
const selected = selectedBoardCards.includes(boardCardId ?? ''); const selected = selectedBoardCards.includes(boardCardId ?? '');
@ -131,25 +124,6 @@ export function CompanyBoardCard() {
} }
} }
const handleCardUpdate = useCallback(
async (pipelineProgress: PipelineProgressForBoard) => {
await updatePipelineProgress({
variables: {
id: pipelineProgress.id,
amount: pipelineProgress.amount,
closeDate: pipelineProgress.closeDate,
probability: pipelineProgress.probability,
pointOfContactId: pipelineProgress.pointOfContactId || undefined,
},
refetchQueries: [
getOperationName(GET_PIPELINE_PROGRESS) ?? '',
getOperationName(GET_PIPELINES) ?? '',
],
});
},
[updatePipelineProgress],
);
if (!company || !pipelineProgress) { if (!company || !pipelineProgress) {
return null; return null;
} }
@ -171,71 +145,40 @@ export function CompanyBoardCard() {
} }
return ( return (
<StyledBoardCardWrapper> <EntityUpdateMutationHookContext.Provider
<StyledBoardCard value={useUpdateOnePipelineProgressMutation}
selected={selected} >
onClick={() => setSelected(!selected)} <StyledBoardCardWrapper>
> <StyledBoardCard
<StyledBoardCardHeader> selected={selected}
<CompanyChip onClick={() => setSelected(!selected)}
id={company.id} >
name={company.name} <StyledBoardCardHeader>
pictureUrl={getLogoUrlFromDomainName(company.domainName)} <CompanyChip
variant={EntityChipVariant.Transparent} id={company.id}
/> name={company.name}
<StyledCheckboxContainer className="checkbox-container"> pictureUrl={getLogoUrlFromDomainName(company.domainName)}
<Checkbox variant={EntityChipVariant.Transparent}
checked={selected}
onChange={() => setSelected(!selected)}
variant={CheckboxVariant.Secondary}
/> />
</StyledCheckboxContainer> <StyledCheckboxContainer className="checkbox-container">
</StyledBoardCardHeader> <Checkbox
<StyledBoardCardBody> checked={selected}
<PreventSelectOnClickContainer> onChange={() => setSelected(!selected)}
<DateEditableField variant={CheckboxVariant.Secondary}
icon={<IconCalendarEvent />} />
value={pipelineProgress.closeDate} </StyledCheckboxContainer>
onSubmit={(value) => </StyledBoardCardHeader>
handleCardUpdate({ <StyledBoardCardBody>
...pipelineProgress, {fieldsDefinitions.map((viewField) => {
closeDate: value, return (
}) <PreventSelectOnClickContainer key={viewField.id}>
} <GenericEditableField viewField={viewField} />
/> </PreventSelectOnClickContainer>
</PreventSelectOnClickContainer> );
<PreventSelectOnClickContainer> })}
<NumberEditableField </StyledBoardCardBody>
icon={<IconCurrencyDollar />} </StyledBoardCard>
placeholder="Opportunity amount" </StyledBoardCardWrapper>
value={pipelineProgress.amount} </EntityUpdateMutationHookContext.Provider>
onSubmit={(value) =>
handleCardUpdate({
...pipelineProgress,
amount: value,
})
}
/>
</PreventSelectOnClickContainer>
<PreventSelectOnClickContainer>
<ProbabilityEditableField
icon={<IconProgressCheck />}
value={pipelineProgress.probability}
onSubmit={(value) => {
handleCardUpdate({
...pipelineProgress,
probability: value,
});
}}
/>
</PreventSelectOnClickContainer>
<PreventSelectOnClickContainer>
<PipelineProgressPointOfContactEditableField
pipelineProgress={pipelineProgress}
/>
</PreventSelectOnClickContainer>
</StyledBoardCardBody>
</StyledBoardCard>
</StyledBoardCardWrapper>
); );
} }

View File

@ -1,24 +1,13 @@
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil'; import { useRecoilState, useSetRecoilState } from 'recoil';
import { useInitializeCompanyBoardFilters } from '@/companies/hooks/useInitializeCompanyBoardFilters'; import { pipelineViewFields } from '@/pipeline/constants/pipelineViewFields';
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState'; import { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState';
import {
CompanyForBoard,
CompanyProgress,
PipelineProgressForBoard,
} from '@/companies/types/CompanyProgress';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState'; import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState';
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause'; import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { import {
GetPipelineProgressQuery,
PipelineProgressableType, PipelineProgressableType,
PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By, PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By,
} from '~/generated/graphql'; } from '~/generated/graphql';
@ -29,83 +18,44 @@ import {
useGetPipelinesQuery, useGetPipelinesQuery,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
import { CompanyBoardContext } from '../states/CompanyBoardContext'; import { CompanyBoardContext } from '../states/CompanyBoardContext';
export function HooksCompanyBoard({ export function HooksCompanyBoard({
availableFilters,
orderBy, orderBy,
}: { }: {
availableFilters: FilterDefinition[];
orderBy: PipelineProgresses_Order_By[]; orderBy: PipelineProgresses_Order_By[];
}) { }) {
useInitializeCompanyBoardFilters({ const setFieldsDefinitionsState = useSetRecoilState(fieldsDefinitionsState);
availableFilters,
});
const [currentPipeline] = useRecoilState(currentPipelineState); useEffect(() => {
const [, setBoardColumns] = useRecoilState(boardColumnsState); setFieldsDefinitionsState(pipelineViewFields);
});
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState); const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
const updateBoardColumns = useRecoilCallback( const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext);
({ set, snapshot }) =>
(pipeline: Pipeline) => {
const currentPipeline = snapshot
.getLoadable(currentPipelineState)
.valueOrThrow();
const currentBoardColumns = snapshot const updateCompanyBoard = useUpdateCompanyBoard();
.getLoadable(boardColumnsState)
.valueOrThrow();
if (JSON.stringify(pipeline) !== JSON.stringify(currentPipeline)) { const { data: pipelineData, loading: loadingGetPipelines } =
set(currentPipelineState, pipeline); useGetPipelinesQuery({
} variables: {
where: {
const pipelineStages = pipeline?.pipelineStages ?? []; pipelineProgressableType: {
equals: PipelineProgressableType.Company,
const orderedPipelineStages = [...pipelineStages].sort((a, b) => { },
if (!a.index || !b.index) return 0; },
return a.index - b.index;
});
const newBoardColumns: BoardColumnDefinition[] =
orderedPipelineStages?.map((pipelineStage) => ({
id: pipelineStage.id,
title: pipelineStage.name,
colorCode: pipelineStage.color,
index: pipelineStage.index ?? 0,
}));
if (
JSON.stringify(currentBoardColumns) !==
JSON.stringify(newBoardColumns)
) {
setBoardColumns(newBoardColumns);
}
}, },
[], });
);
useGetPipelinesQuery({ const pipeline = pipelineData?.findManyPipeline[0] as Pipeline | undefined;
variables: {
where: {
pipelineProgressableType: { equals: PipelineProgressableType.Company },
},
},
onCompleted: async (data) => {
const pipeline = data?.findManyPipeline[0] as Pipeline;
updateBoardColumns(pipeline); const pipelineStageIds = pipeline?.pipelineStages
},
});
const pipelineStageIds = currentPipeline?.pipelineStages
?.map((pipelineStage) => pipelineStage.id) ?.map((pipelineStage) => pipelineStage.id)
.flat(); .flat();
const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext);
const whereFilters = useMemo(() => { const whereFilters = useMemo(() => {
return { return {
AND: [ AND: [
@ -115,114 +65,52 @@ export function HooksCompanyBoard({
}; };
}, [filters, pipelineStageIds]) as any; }, [filters, pipelineStageIds]) as any;
const updateBoardCardIds = useRecoilCallback( const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
({ snapshot, set }) =>
(
pipelineProgresses: GetPipelineProgressQuery['findManyPipelineProgress'],
) => {
const boardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
for (const boardColumn of boardColumns) { const { data: pipelineProgressData, loading: loadingGetPipelineProgress } =
const boardCardIds = pipelineProgresses useGetPipelineProgressQuery({
.filter( variables: {
(pipelineProgressToFilter) => where: whereFilters,
pipelineProgressToFilter.pipelineStageId === boardColumn.id, orderBy,
)
.map((pipelineProgress) => pipelineProgress.id);
set(boardCardIdsByColumnIdFamilyState(boardColumn.id), boardCardIds);
}
}, },
[], onCompleted: (data) => {
); const pipelineProgresses = data?.findManyPipelineProgress || [];
const pipelineProgressesQuery = useGetPipelineProgressQuery({ updateCompanyBoardCardIds(pipelineProgresses);
variables: {
where: whereFilters,
orderBy,
},
onCompleted: (data) => {
const pipelineProgresses = data?.findManyPipelineProgress || [];
updateBoardCardIds(pipelineProgresses); setIsBoardLoaded(true);
},
});
setIsBoardLoaded(true); const pipelineProgresses = useMemo(() => {
}, return pipelineProgressData?.findManyPipelineProgress || [];
}); }, [pipelineProgressData]);
const pipelineProgresses = const { data: companiesData, loading: loadingGetCompanies } =
pipelineProgressesQuery.data?.findManyPipelineProgress || []; useGetCompaniesQuery({
variables: {
const entitiesQueryResult = useGetCompaniesQuery({ where: {
variables: { id: {
where: { in: pipelineProgresses.map((item) => item.companyId || ''),
id: { },
in: pipelineProgresses.map((item) => item.companyId || ''),
}, },
}, },
}, });
});
const indexCompanyByIdReducer = (
acc: { [key: string]: CompanyForBoard },
company: CompanyForBoard,
) => ({
...acc,
[company.id]: company,
});
const companiesDict =
entitiesQueryResult.data?.companies.reduce(
indexCompanyByIdReducer,
{} as { [key: string]: CompanyForBoard },
) || {};
const indexPipelineProgressByIdReducer = (
acc: {
[key: string]: CompanyProgress;
},
pipelineProgress: PipelineProgressForBoard,
) => {
const company =
pipelineProgress.companyId && companiesDict[pipelineProgress.companyId];
if (!company) return acc;
return {
...acc,
[pipelineProgress.id]: {
pipelineProgress,
company,
},
};
};
const companyBoardIndex = pipelineProgresses.reduce(
indexPipelineProgressByIdReducer,
{} as { [key: string]: CompanyProgress },
);
const synchronizeCompanyProgresses = useRecoilCallback(
({ snapshot, set }) =>
(companyBoardIndex: { [key: string]: CompanyProgress }) => {
Object.entries(companyBoardIndex).forEach(([id, companyProgress]) => {
if (
JSON.stringify(
snapshot.getLoadable(companyProgressesFamilyState(id)).getValue(),
) !== JSON.stringify(companyProgress)
) {
set(companyProgressesFamilyState(id), companyProgress);
}
});
},
[],
);
const loading = const loading =
entitiesQueryResult.loading || pipelineProgressesQuery.loading; loadingGetPipelines || loadingGetPipelineProgress || loadingGetCompanies;
useEffect(() => { useEffect(() => {
!loading && synchronizeCompanyProgresses(companyBoardIndex); if (!loading && pipeline && pipelineProgresses && companiesData) {
}, [loading, companyBoardIndex, synchronizeCompanyProgresses]); updateCompanyBoard(pipeline, pipelineProgresses, companiesData.companies);
}
}, [
loading,
pipeline,
pipelineProgresses,
companiesData,
updateCompanyBoard,
]);
return <></>; return <></>;
} }

View File

@ -1,3 +1,13 @@
import {
ViewFieldChipMetadata,
ViewFieldDateMetadata,
ViewFieldDefinition,
ViewFieldMetadata,
ViewFieldNumberMetadata,
ViewFieldRelationMetadata,
ViewFieldTextMetadata,
ViewFieldURLMetadata,
} from '@/ui/editable-field/types/ViewField';
import { import {
IconBrandLinkedin, IconBrandLinkedin,
IconBuildingSkyscraper, IconBuildingSkyscraper,
@ -8,16 +18,6 @@ import {
IconUsers, IconUsers,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import {
ViewFieldChipMetadata,
ViewFieldDateMetadata,
ViewFieldDefinition,
ViewFieldMetadata,
ViewFieldNumberMetadata,
ViewFieldRelationMetadata,
ViewFieldTextMetadata,
ViewFieldURLMetadata,
} from '@/ui/table/types/ViewField';
export const companyViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [ export const companyViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
{ {

View File

@ -0,0 +1,30 @@
import { useRecoilCallback } from 'recoil';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
import { GetPipelineProgressQuery } from '~/generated/graphql';
export function useUpdateCompanyBoardCardIds() {
return useRecoilCallback(
({ snapshot, set }) =>
(
pipelineProgresses: GetPipelineProgressQuery['findManyPipelineProgress'],
) => {
const boardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
for (const boardColumn of boardColumns) {
const boardCardIds = pipelineProgresses
.filter(
(pipelineProgressToFilter) =>
pipelineProgressToFilter.pipelineStageId === boardColumn.id,
)
.map((pipelineProgress) => pipelineProgress.id);
set(boardCardIdsByColumnIdFamilyState(boardColumn.id), boardCardIds);
}
},
[],
);
}

View File

@ -0,0 +1,133 @@
import { useRecoilCallback } from 'recoil';
import { currentPipelineState } from '@/pipeline/states/currentPipelineState';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
import { Pipeline } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState';
import {
CompanyForBoard,
CompanyProgressDict,
PipelineProgressForBoard,
} from '../types/CompanyProgress';
export function useUpdateCompanyBoard() {
return useRecoilCallback(
({ set, snapshot }) =>
(
pipeline: Pipeline,
pipelineProgresses: (PipelineProgressForBoard & {
pipelineStageId: string;
})[],
companies: CompanyForBoard[],
) => {
const indexCompanyByIdReducer = (
acc: { [key: string]: CompanyForBoard },
company: CompanyForBoard,
) => ({
...acc,
[company.id]: company,
});
const companiesDict =
companies.reduce(
indexCompanyByIdReducer,
{} as { [key: string]: CompanyForBoard },
) ?? {};
const indexPipelineProgressByIdReducer = (
acc: CompanyProgressDict,
pipelineProgress: PipelineProgressForBoard,
) => {
const company =
pipelineProgress.companyId &&
companiesDict[pipelineProgress.companyId];
if (!company) return acc;
return {
...acc,
[pipelineProgress.id]: {
pipelineProgress,
company,
},
};
};
const companyBoardIndex = pipelineProgresses.reduce(
indexPipelineProgressByIdReducer,
{} as CompanyProgressDict,
);
for (const [id, companyProgress] of Object.entries(companyBoardIndex)) {
const currentCompanyProgress = snapshot
.getLoadable(companyProgressesFamilyState(id))
.valueOrThrow();
if (!isDeeplyEqual(currentCompanyProgress, companyProgress)) {
set(companyProgressesFamilyState(id), companyProgress);
set(
genericEntitiesFamilyState(id),
companyProgress.pipelineProgress,
);
}
}
const currentPipeline = snapshot
.getLoadable(currentPipelineState)
.valueOrThrow();
const currentBoardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
if (!isDeeplyEqual(pipeline, currentPipeline)) {
set(currentPipelineState, pipeline);
}
const pipelineStages = pipeline?.pipelineStages ?? [];
const orderedPipelineStages = [...pipelineStages].sort((a, b) => {
if (!a.index || !b.index) return 0;
return a.index - b.index;
});
const newBoardColumns: BoardColumnDefinition[] =
orderedPipelineStages?.map((pipelineStage) => ({
id: pipelineStage.id,
title: pipelineStage.name,
colorCode: pipelineStage.color,
index: pipelineStage.index ?? 0,
}));
if (!isDeeplyEqual(currentBoardColumns, newBoardColumns)) {
set(boardColumnsState, newBoardColumns);
}
for (const boardColumn of newBoardColumns) {
const boardCardIds = pipelineProgresses
.filter(
(pipelineProgressToFilter) =>
pipelineProgressToFilter.pipelineStageId === boardColumn.id,
)
.map((pipelineProgress) => pipelineProgress.id);
const currentBoardCardIds = snapshot
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
.valueOrThrow();
if (!isDeeplyEqual(currentBoardCardIds, boardCardIds)) {
set(
boardCardIdsByColumnIdFamilyState(boardColumn.id),
boardCardIds,
);
}
}
},
[],
);
}

View File

@ -6,6 +6,6 @@ export const companyBoardIndexState = atomFamily<
CompanyProgress | undefined, CompanyProgress | undefined,
string string
>({ >({
key: 'currentPipelineState', key: 'companyBoardIndexState',
default: undefined, default: undefined,
}); });

View File

@ -7,8 +7,8 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi
import { useSearchPeopleQuery } from '~/generated/graphql'; import { useSearchPeopleQuery } from '~/generated/graphql';
export type OwnProps = { export type OwnProps = {
personId: string; personId: string | null;
onSubmit: (newPersonId: string | null) => void; onSubmit: (newPersonId: PersonForSelect | null) => void;
onCancel?: () => void; onCancel?: () => void;
}; };
@ -23,7 +23,7 @@ export function PeoplePicker({ personId, onSubmit, onCancel }: OwnProps) {
const people = useFilteredSearchEntityQuery({ const people = useFilteredSearchEntityQuery({
queryHook: useSearchPeopleQuery, queryHook: useSearchPeopleQuery,
selectedIds: [personId], selectedIds: [personId ?? ''],
searchFilter: searchFilter, searchFilter: searchFilter,
mappingFunction: (person) => ({ mappingFunction: (person) => ({
entityType: Entity.Person, entityType: Entity.Person,
@ -39,7 +39,7 @@ export function PeoplePicker({ personId, onSubmit, onCancel }: OwnProps) {
async function handleEntitySelected( async function handleEntitySelected(
selectedPerson: PersonForSelect | null | undefined, selectedPerson: PersonForSelect | null | undefined,
) { ) {
onSubmit(selectedPerson?.id ?? null); onSubmit(selectedPerson ?? null);
} }
return ( return (

View File

@ -1,3 +1,13 @@
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
ViewFieldDoubleTextChipMetadata,
ViewFieldMetadata,
ViewFieldPhoneMetadata,
ViewFieldRelationMetadata,
ViewFieldTextMetadata,
ViewFieldURLMetadata,
} from '@/ui/editable-field/types/ViewField';
import { import {
IconBrandLinkedin, IconBrandLinkedin,
IconBriefcase, IconBriefcase,
@ -9,16 +19,6 @@ import {
IconUser, IconUser,
} from '@/ui/icon/index'; } from '@/ui/icon/index';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
ViewFieldDoubleTextChipMetadata,
ViewFieldMetadata,
ViewFieldPhoneMetadata,
ViewFieldRelationMetadata,
ViewFieldTextMetadata,
ViewFieldURLMetadata,
} from '@/ui/table/types/ViewField';
export const peopleViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [ export const peopleViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
{ {

View File

@ -1,94 +0,0 @@
import { getOperationName } from '@apollo/client/utilities';
import { Key } from 'ts-key-enum';
import { useFilteredSearchPeopleQuery } from '@/people/queries';
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import {
Person,
PipelineProgress,
useUpdateOnePipelineProgressMutation,
} from '~/generated/graphql';
import { GET_PIPELINE_PROGRESS, GET_PIPELINES } from '../queries';
export type OwnProps = {
pipelineProgress: Pick<PipelineProgress, 'id'> & {
pointOfContact?: Pick<Person, 'id'> | null;
};
onSubmit?: () => void;
onCancel?: () => void;
};
export function PipelineProgressPointOfContactPicker({
pipelineProgress,
onSubmit,
onCancel,
}: OwnProps) {
const [, setIsCreating] = useRecoilScopedState(isCreateModeScopedState);
const [searchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState,
);
const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation();
const people = useFilteredSearchPeopleQuery({
searchFilter,
selectedIds: pipelineProgress.pointOfContact?.id
? [pipelineProgress.pointOfContact.id]
: [],
});
async function handleEntitySelected(
entity: EntityForSelect | null | undefined,
) {
if (!entity) {
return;
}
await updatePipelineProgress({
variables: {
...pipelineProgress,
pointOfContactId: entity.id,
},
refetchQueries: [
getOperationName(GET_PIPELINE_PROGRESS) ?? '',
getOperationName(GET_PIPELINES) ?? '',
],
});
onSubmit?.();
}
function handleCreate() {
setIsCreating(true);
onSubmit?.();
}
useScopedHotkeys(
Key.Escape,
() => {
onCancel && onCancel();
},
RelationPickerHotkeyScope.RelationPicker,
[],
);
return (
<SingleEntitySelect
onCreate={handleCreate}
onCancel={onCancel}
onEntitySelected={handleEntitySelected}
entities={{
entitiesToSelect: people.entitiesToSelect,
selectedEntity: people.selectedEntities[0],
loading: people.loading,
}}
/>
);
}

View File

@ -0,0 +1,67 @@
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
ViewFieldMetadata,
ViewFieldNumberMetadata,
ViewFieldProbabilityMetadata,
ViewFieldRelationMetadata,
} from '@/ui/editable-field/types/ViewField';
import {
IconCalendarEvent,
IconCurrencyDollar,
IconProgressCheck,
IconUser,
} from '@/ui/icon';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
export const pipelineViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
{
id: 'closeDate',
columnLabel: 'Close Date',
columnIcon: <IconCalendarEvent />,
columnSize: 150,
columnOrder: 4,
metadata: {
type: 'date',
fieldName: 'closeDate',
},
isVisible: true,
} satisfies ViewFieldDefinition<ViewFieldDateMetadata>,
{
id: 'amount',
columnLabel: 'Amount',
columnIcon: <IconCurrencyDollar />,
columnSize: 150,
columnOrder: 4,
metadata: {
type: 'number',
fieldName: 'amount',
},
isVisible: true,
} satisfies ViewFieldDefinition<ViewFieldNumberMetadata>,
{
id: 'probability',
columnLabel: 'Probability',
columnIcon: <IconProgressCheck />,
columnSize: 150,
columnOrder: 4,
metadata: {
type: 'probability',
fieldName: 'probability',
},
isVisible: true,
} satisfies ViewFieldDefinition<ViewFieldProbabilityMetadata>,
{
id: 'pointOfContact',
columnLabel: 'Point of Contact',
columnIcon: <IconUser />,
columnSize: 150,
columnOrder: 4,
metadata: {
type: 'relation',
fieldName: 'pointOfContact',
relationType: Entity.Person,
},
isVisible: true,
} satisfies ViewFieldDefinition<ViewFieldRelationMetadata>,
];

View File

@ -1,53 +0,0 @@
import { PersonChip } from '@/people/components/PersonChip';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconUser } from '@/ui/icon';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Person, PipelineProgress } from '~/generated/graphql';
import { PipelineProgressPointOfContactPickerFieldEditMode } from './PipelineProgressPointOfContactPickerFieldEditMode';
type OwnProps = {
pipelineProgress: Pick<PipelineProgress, 'id' | 'pointOfContactId'> & {
pointOfContact?: Pick<Person, 'id' | 'displayName' | 'avatarUrl'> | null;
};
};
export function PipelineProgressPointOfContactEditableField({
pipelineProgress,
}: OwnProps) {
return (
<RecoilScope SpecificContext={FieldContext}>
<RecoilScope>
<EditableField
useEditButton
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
iconLabel={<IconUser />}
editModeContent={
<PipelineProgressPointOfContactPickerFieldEditMode
pipelineProgress={pipelineProgress}
/>
}
displayModeContent={
pipelineProgress.pointOfContact ? (
<PersonChip
id={pipelineProgress.pointOfContact.id}
name={pipelineProgress.pointOfContact.displayName}
pictureUrl={
pipelineProgress.pointOfContact.avatarUrl ?? undefined
}
/>
) : (
<></>
)
}
isDisplayModeContentEmpty={!pipelineProgress.pointOfContact}
isDisplayModeFixHeight
/>
</RecoilScope>
</RecoilScope>
);
}

View File

@ -1,50 +0,0 @@
import styled from '@emotion/styled';
import { PipelineProgressPointOfContactPicker } from '@/pipeline/components/PipelineProgressPointOfContactPicker';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { Person, PipelineProgress } from '~/generated/graphql';
const PipelineProgressPointOfContactPickerContainer = styled.div`
left: 0px;
position: absolute;
top: -8px;
`;
export type OwnProps = {
pipelineProgress: Pick<PipelineProgress, 'id'> & {
pointOfContact?: Pick<
Person,
'id' | 'firstName' | 'lastName' | 'displayName'
> | null;
};
onSubmit?: () => void;
onCancel?: () => void;
};
export function PipelineProgressPointOfContactPickerFieldEditMode({
pipelineProgress,
onSubmit,
onCancel,
}: OwnProps) {
const { closeEditableField } = useEditableField();
function handleSubmit() {
closeEditableField();
onSubmit?.();
}
function handleCancel() {
closeEditableField();
onCancel?.();
}
return (
<PipelineProgressPointOfContactPickerContainer>
<PipelineProgressPointOfContactPicker
pipelineProgress={pipelineProgress}
onCancel={handleCancel}
onSubmit={handleSubmit}
/>
</PipelineProgressPointOfContactPickerContainer>
);
}

View File

@ -1,79 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconCurrencyDollar } from '@/ui/icon';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import {
PipelineProgress,
useUpdateOnePipelineProgressMutation,
} from '~/generated/graphql';
type OwnProps = {
progress: Pick<PipelineProgress, 'id' | 'amount'>;
};
export function PipelineProgressAmountEditableField({ progress }: OwnProps) {
const [internalValue, setInternalValue] = useState(
progress.amount?.toString(),
);
const [updateOnePipelineProgress] = useUpdateOnePipelineProgressMutation();
useEffect(() => {
setInternalValue(progress.amount?.toString());
}, [progress.amount]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
if (!internalValue) return;
try {
const numberValue = parseInt(internalValue);
if (isNaN(numberValue)) {
throw new Error('Not a number');
}
await updateOnePipelineProgress({
variables: {
id: progress.id,
amount: numberValue,
},
});
setInternalValue(numberValue.toString());
} catch {
handleCancel();
}
}
async function handleCancel() {
setInternalValue(progress.amount?.toString());
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={<IconCurrencyDollar />}
editModeContent={
<TextInputEdit
placeholder={'Amount'}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue}
/>
</RecoilScope>
);
}

View File

@ -20,24 +20,14 @@ export const UPDATE_PIPELINE_STAGE = gql`
export const UPDATE_PIPELINE_PROGRESS = gql` export const UPDATE_PIPELINE_PROGRESS = gql`
mutation UpdateOnePipelineProgress( mutation UpdateOnePipelineProgress(
$id: String $data: PipelineProgressUpdateInput!
$amount: Int $where: PipelineProgressWhereUniqueInput!
$closeDate: DateTime
$probability: Int
$pointOfContactId: String
) { ) {
updateOnePipelineProgress( updateOnePipelineProgress(where: $where, data: $data) {
where: { id: $id }
data: {
amount: $amount
closeDate: $closeDate
probability: $probability
pointOfContact: { connect: { id: $pointOfContactId } }
}
) {
id id
amount amount
closeDate closeDate
probability
} }
} }
`; `;

View File

@ -1,10 +1,8 @@
import React from 'react'; import React from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { Tag } from '@/ui/tag/components/Tag'; import { Tag } from '@/ui/tag/components/Tag';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope'; import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
@ -77,7 +75,6 @@ const StyledNumChildren = styled.div`
type OwnProps = { type OwnProps = {
color?: string; color?: string;
title: string; title: string;
pipelineStageId?: string;
onTitleEdit: (title: string) => void; onTitleEdit: (title: string) => void;
onColumnColorEdit: (color: string) => void; onColumnColorEdit: (color: string) => void;
totalAmount?: number; totalAmount?: number;
@ -104,13 +101,6 @@ export function BoardColumn({
goBackToPreviousHotkeyScope, goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope(); } = usePreviousHotkeyScope();
useScopedHotkeys(
[Key.Escape, Key.Enter],
handleClose,
BoardColumnHotkeyScope.BoardColumn,
[],
);
function handleTitleClick() { function handleTitleClick() {
setIsBoardColumnMenuOpen(true); setIsBoardColumnMenuOpen(true);
setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, { setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, {
@ -132,7 +122,7 @@ export function BoardColumn({
</StyledHeader> </StyledHeader>
{isBoardColumnMenuOpen && ( {isBoardColumnMenuOpen && (
<BoardColumnMenu <BoardColumnMenu
onClose={() => setIsBoardColumnMenuOpen(false)} onClose={handleClose}
onTitleEdit={onTitleEdit} onTitleEdit={onTitleEdit}
onColumnColorEdit={onColumnColorEdit} onColumnColorEdit={onColumnColorEdit}
title={title} title={title}

View File

@ -1,13 +1,17 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconPencil } from '@tabler/icons-react'; import { IconPencil } from '@tabler/icons-react';
import { Key } from 'ts-key-enum';
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem'; import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { icon } from '@/ui/theme/constants/icon'; import { icon } from '@/ui/theme/constants/icon';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu'; import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
const StyledMenuContainer = styled.div` const StyledMenuContainer = styled.div`
@ -39,6 +43,13 @@ export function BoardColumnMenu({
callback: onClose, callback: onClose,
}); });
useScopedHotkeys(
[Key.Escape, Key.Enter],
onClose,
BoardColumnHotkeyScope.BoardColumn,
[],
);
return ( return (
<StyledMenuContainer ref={boardColumnMenuRef}> <StyledMenuContainer ref={boardColumnMenuRef}>
<DropdownMenu> <DropdownMenu>

View File

@ -48,6 +48,7 @@ export function EntityBoard({
onEditColumnColor: (columnId: string, color: string) => void; onEditColumnColor: (columnId: string, color: string) => void;
}) { }) {
const [boardColumns] = useRecoilState(boardColumnsState); const [boardColumns] = useRecoilState(boardColumnsState);
const theme = useTheme(); const theme = useTheme();
const [updatePipelineProgressStage] = const [updatePipelineProgressStage] =
useUpdateOnePipelineProgressStageMutation(); useUpdateOnePipelineProgressStageMutation();

View File

@ -4,19 +4,15 @@ import { BoardOptions } from '../types/BoardOptions';
export function EntityBoardCard({ export function EntityBoardCard({
boardOptions, boardOptions,
pipelineProgressId, cardId,
index, index,
}: { }: {
boardOptions: BoardOptions; boardOptions: BoardOptions;
pipelineProgressId: string; cardId: string;
index: number; index: number;
}) { }) {
return ( return (
<Draggable <Draggable key={cardId} draggableId={cardId} index={index}>
key={pipelineProgressId}
draggableId={pipelineProgressId}
index={index}
>
{(draggableProvided) => ( {(draggableProvided) => (
<div <div
ref={draggableProvided?.innerRef} ref={draggableProvided?.innerRef}

View File

@ -84,7 +84,6 @@ export function EntityBoardColumn({
onTitleEdit={handleEditColumnTitle} onTitleEdit={handleEditColumnTitle}
title={column.title} title={column.title}
color={column.colorCode} color={column.colorCode}
pipelineStageId={column.id}
totalAmount={boardColumnTotal} totalAmount={boardColumnTotal}
isFirstColumn={column.index === 0} isFirstColumn={column.index === 0}
numChildren={cardIds.length} numChildren={cardIds.length}
@ -94,7 +93,7 @@ export function EntityBoardColumn({
<BoardCardIdContext.Provider value={cardId} key={cardId}> <BoardCardIdContext.Provider value={cardId} key={cardId}>
<EntityBoardCard <EntityBoardCard
index={index} index={index}
pipelineProgressId={cardId} cardId={cardId}
boardOptions={boardOptions} boardOptions={boardOptions}
/> />
</BoardCardIdContext.Provider> </BoardCardIdContext.Provider>

View File

@ -0,0 +1,9 @@
import { createContext } from 'react';
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '../../editable-field/types/ViewField';
export const FieldDefinitionContext =
createContext<ViewFieldDefinition<ViewFieldMetadata> | null>(null);

View File

@ -0,0 +1,13 @@
import { atom } from 'recoil';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '../../editable-field/types/ViewField';
export const fieldsDefinitionsState = atom<
ViewFieldDefinition<ViewFieldMetadata>[]
>({
key: 'fieldsDefinitionState',
default: [],
});

View File

@ -0,0 +1,49 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
} from '@/ui/editable-field/types/ViewField';
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { parseDate } from '~/utils/date-utils';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { EditableField } from './EditableField';
import { GenericEditableDateFieldEditMode } from './GenericEditableDateFieldEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldDateMetadata>;
};
export function GenericEditableDateField({ viewField }: OwnProps) {
const currentEntityId = useContext(BoardCardIdContext);
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEntityId ?? '',
fieldName: viewField.metadata.fieldName,
}),
);
const internalDateValue = fieldValue
? parseDate(fieldValue).toJSDate()
: null;
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={viewField.columnIcon}
editModeContent={
<GenericEditableDateFieldEditMode viewField={viewField} />
}
displayModeContent={<DateInputDisplay value={internalDateValue} />}
isDisplayModeContentEmpty={!fieldValue}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,44 @@
import { useContext } from 'react';
import { useRecoilState } from 'recoil';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
} from '@/ui/editable-field/types/ViewField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { EditableFieldEditModeDate } from '../variants/components/EditableFieldEditModeDate';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldDateMetadata>;
};
export function GenericEditableDateFieldEditMode({ viewField }: OwnProps) {
const currentEntityId = useContext(BoardCardIdContext);
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
genericEntityFieldFamilySelector({
entityId: currentEntityId ?? '',
fieldName: viewField.metadata.fieldName,
}),
);
const updateField = useUpdateGenericEntityField();
function handleSubmit(newDateISO: string) {
if (newDateISO === fieldValue || !newDateISO) return;
setFieldValue(newDateISO);
if (currentEntityId && updateField && newDateISO) {
updateField(currentEntityId, viewField, newDateISO);
}
}
return (
<EditableFieldEditModeDate value={fieldValue} onChange={handleSubmit} />
);
}

View File

@ -0,0 +1,35 @@
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import { isViewFieldDate } from '../types/guards/isViewFieldDate';
import { isViewFieldNumber } from '../types/guards/isViewFieldNumber';
import { isViewFieldProbability } from '../types/guards/isViewFieldProbability';
import { isViewFieldRelation } from '../types/guards/isViewFieldRelation';
import { GenericEditableDateField } from './GenericEditableDateField';
import { GenericEditableNumberField } from './GenericEditableNumberField';
import { GenericEditableRelationField } from './GenericEditableRelationField';
import { ProbabilityEditableField } from './ProbabilityEditableField';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldMetadata>;
};
export function GenericEditableField({ viewField: fieldDefinition }: OwnProps) {
if (isViewFieldDate(fieldDefinition)) {
return <GenericEditableDateField viewField={fieldDefinition} />;
} else if (isViewFieldNumber(fieldDefinition)) {
return <GenericEditableNumberField viewField={fieldDefinition} />;
} else if (isViewFieldRelation(fieldDefinition)) {
return <GenericEditableRelationField viewField={fieldDefinition} />;
} else if (isViewFieldProbability(fieldDefinition)) {
return <ProbabilityEditableField viewField={fieldDefinition} />;
} else {
console.warn(
`Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableField`,
);
return <></>;
}
}

View File

@ -0,0 +1,43 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import {
ViewFieldDefinition,
ViewFieldNumberMetadata,
} from '@/ui/editable-field/types/ViewField';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { EditableField } from './EditableField';
import { GenericEditableNumberFieldEditMode } from './GenericEditableNumberFieldEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldNumberMetadata>;
};
export function GenericEditableNumberField({ viewField }: OwnProps) {
const currentEntityId = useContext(BoardCardIdContext);
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEntityId ?? '',
fieldName: viewField.metadata.fieldName,
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={viewField.columnIcon}
editModeContent={
<GenericEditableNumberFieldEditMode viewField={viewField} />
}
displayModeContent={fieldValue}
isDisplayModeContentEmpty={!fieldValue}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,78 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilState } from 'recoil';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import {
ViewFieldDefinition,
ViewFieldNumberMetadata,
} from '@/ui/editable-field/types/ViewField';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '~/utils/cast-as-integer-or-null';
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldNumberMetadata>;
};
export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) {
const currentEntityId = useContext(BoardCardIdContext);
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<number | null>(
genericEntityFieldFamilySelector({
entityId: currentEntityId ?? '',
fieldName: viewField.metadata.fieldName,
}),
);
const [internalValue, setInternalValue] = useState(
fieldValue ? fieldValue.toString() : '',
);
const updateField = useUpdateGenericEntityField();
function handleSubmit() {
if (!canBeCastAsIntegerOrNull(internalValue)) {
return;
}
if (internalValue === fieldValue) return;
setFieldValue(castAsIntegerOrNull(internalValue));
if (currentEntityId && updateField) {
updateField(
currentEntityId,
viewField,
castAsIntegerOrNull(internalValue),
);
}
}
function onCancel() {
setFieldValue(fieldValue);
}
function handleChange(newValue: string) {
setInternalValue(newValue);
}
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
return (
<div ref={wrapperRef}>
<TextInputEdit
autoFocus
value={internalValue ? internalValue.toString() : ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
</div>
);
}

View File

@ -0,0 +1,80 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { PersonChip } from '@/people/components/PersonChip';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
} from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { EditableField } from './EditableField';
import { GenericEditableRelationFieldEditMode } from './GenericEditableRelationFieldEditMode';
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldRelationMetadata>;
};
function RelationChip({
fieldDefinition,
fieldValue,
}: {
fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
fieldValue: any | null;
}) {
switch (fieldDefinition.metadata.relationType) {
case Entity.Person: {
return (
<PersonChip
id={fieldValue?.id ?? ''}
name={fieldValue?.displayName ?? ''}
pictureUrl={fieldValue?.avatarUrl ?? ''}
/>
);
}
default:
console.warn(
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`,
);
return <> </>;
}
}
export function GenericEditableRelationField({ viewField }: OwnProps) {
const currentEntityId = useContext(BoardCardIdContext);
const fieldValue = useRecoilValue<any | null>(
genericEntityFieldFamilySelector({
entityId: currentEntityId ?? '',
fieldName: viewField.metadata.fieldName,
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<RecoilScope>
<EditableField
useEditButton
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
iconLabel={viewField.columnIcon}
editModeContent={
<GenericEditableRelationFieldEditMode viewField={viewField} />
}
displayModeContent={
<RelationChip fieldDefinition={viewField} fieldValue={fieldValue} />
}
isDisplayModeContentEmpty={!fieldValue}
isDisplayModeFixHeight
/>
</RecoilScope>
</RecoilScope>
);
}

View File

@ -0,0 +1,102 @@
import { useContext } from 'react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { PeoplePicker } from '@/people/components/PeoplePicker';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
ViewFieldRelationValue,
} from '@/ui/editable-field/types/ViewField';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useEditableField } from '../hooks/useEditableField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
const RelationPickerContainer = styled.div`
left: 0px;
position: absolute;
top: -8px;
`;
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldRelationMetadata>;
};
function RelationPicker({
fieldDefinition,
fieldValue,
handleEntitySubmit,
handleCancel,
}: {
fieldDefinition: ViewFieldDefinition<ViewFieldRelationMetadata>;
fieldValue: ViewFieldRelationValue;
handleEntitySubmit: (newRelationId: EntityForSelect | null) => void;
handleCancel: () => void;
}) {
switch (fieldDefinition.metadata.relationType) {
case Entity.Person: {
return (
<PeoplePicker
personId={fieldValue?.id ?? null}
onSubmit={handleEntitySubmit}
onCancel={handleCancel}
/>
);
}
default:
console.warn(
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`,
);
return <> </>;
}
}
export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) {
const currentEntityId = useContext(BoardCardIdContext);
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<any | null>(
genericEntityFieldFamilySelector({
entityId: currentEntityId ?? '',
fieldName: viewField.metadata.fieldName,
}),
);
const updateField = useUpdateGenericEntityField();
const { closeEditableField } = useEditableField();
function handleSubmit(newRelation: EntityForSelect | null) {
if (newRelation?.id === fieldValue?.id) return;
setFieldValue({
id: newRelation?.id ?? null,
displayName: newRelation?.name ?? null,
avatarUrl: newRelation?.avatarUrl ?? null,
});
if (currentEntityId && updateField) {
updateField(currentEntityId, viewField, newRelation);
}
closeEditableField();
}
function handleCancel() {
closeEditableField();
}
return (
<RelationPickerContainer>
<RelationPicker
fieldDefinition={viewField}
fieldValue={fieldValue}
handleEntitySubmit={handleSubmit}
handleCancel={handleCancel}
/>
</RelationPickerContainer>
);
}

View File

@ -1,25 +1,27 @@
import { EditableField } from '@/ui/editable-field/components/EditableField'; import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext'; import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import {
ViewFieldDefinition,
ViewFieldProbabilityMetadata,
} from '@/ui/editable-field/types/ViewField';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { ProbabilityFieldEditMode } from './ProbabilityFieldEditMode'; import { ProbabilityEditableFieldEditMode } from './ProbabilityEditableFieldEditMode';
type OwnProps = { type OwnProps = {
icon?: React.ReactNode; viewField: ViewFieldDefinition<ViewFieldProbabilityMetadata>;
value: number | null | undefined;
onSubmit?: (newValue: number) => void;
}; };
export function ProbabilityEditableField({ icon, value, onSubmit }: OwnProps) { export function ProbabilityEditableField({ viewField }: OwnProps) {
return ( return (
<RecoilScope SpecificContext={FieldContext}> <RecoilScope SpecificContext={FieldContext}>
<EditableField <EditableField
iconLabel={icon} iconLabel={viewField.columnIcon}
displayModeContent={
<ProbabilityEditableFieldEditMode viewField={viewField} />
}
displayModeContentOnly displayModeContentOnly
disableHoverEffect disableHoverEffect
displayModeContent={
<ProbabilityFieldEditMode value={value ?? 0} onChange={onSubmit} />
}
/> />
</RecoilScope> </RecoilScope>
); );

View File

@ -0,0 +1,136 @@
import { useContext, useState } from 'react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import {
ViewFieldDefinition,
ViewFieldProbabilityMetadata,
} from '../types/ViewField';
const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 100%;
`;
const StyledProgressBarItemContainer = styled.div`
align-items: center;
display: flex;
height: ${({ theme }) => theme.spacing(4)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;
const StyledProgressBarItem = styled.div<{
isFirst: boolean;
isLast: boolean;
isActive: boolean;
}>`
background-color: ${({ theme, isActive }) =>
isActive
? theme.font.color.secondary
: theme.background.transparent.medium};
border-bottom-left-radius: ${({ theme, isFirst }) =>
isFirst ? theme.border.radius.sm : theme.border.radius.xs};
border-bottom-right-radius: ${({ theme, isLast }) =>
isLast ? theme.border.radius.sm : theme.border.radius.xs};
border-top-left-radius: ${({ theme, isFirst }) =>
isFirst ? theme.border.radius.sm : theme.border.radius.xs};
border-top-right-radius: ${({ theme, isLast }) =>
isLast ? theme.border.radius.sm : theme.border.radius.xs};
height: ${({ theme }) => theme.spacing(2)};
width: ${({ theme }) => theme.spacing(3)};
`;
const StyledProgressBarContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
width: 100%;
`;
const StyledLabel = styled.div`
width: ${({ theme }) => theme.spacing(12)};
`;
type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldProbabilityMetadata>;
};
const PROBABILITY_VALUES = [
{ label: '0%', value: 0 },
{ label: '25%', value: 25 },
{ label: '50%', value: 50 },
{ label: '75%', value: 75 },
{ label: '100%', value: 100 },
];
export function ProbabilityEditableFieldEditMode({ viewField }: OwnProps) {
const [nextProbabilityIndex, setNextProbabilityIndex] = useState<
number | null
>(null);
const currentEntityId = useContext(BoardCardIdContext);
const [fieldValue, setFieldValue] = useRecoilState<number>(
genericEntityFieldFamilySelector({
entityId: currentEntityId ?? '',
fieldName: viewField.metadata.fieldName,
}),
);
const probabilityIndex = Math.ceil(fieldValue / 25);
const { closeEditableField } = useEditableField();
const updateField = useUpdateGenericEntityField();
function handleChange(newValue: number) {
setFieldValue(newValue);
if (currentEntityId && updateField && newValue) {
updateField(currentEntityId, viewField, newValue);
}
closeEditableField();
}
console.log(probabilityIndex);
return (
<StyledContainer>
<StyledLabel>
{
PROBABILITY_VALUES[
nextProbabilityIndex || nextProbabilityIndex === 0
? nextProbabilityIndex
: probabilityIndex
].label
}
</StyledLabel>
<StyledProgressBarContainer>
{PROBABILITY_VALUES.map((probability, i) => (
<StyledProgressBarItemContainer
key={i}
onClick={() => handleChange(probability.value)}
onMouseEnter={() => setNextProbabilityIndex(i)}
onMouseLeave={() => setNextProbabilityIndex(null)}
>
<StyledProgressBarItem
isActive={
nextProbabilityIndex || nextProbabilityIndex === 0
? i <= nextProbabilityIndex
: i <= probabilityIndex
}
key={probability.label}
isFirst={i === 0}
isLast={i === PROBABILITY_VALUES.length - 1}
/>
</StyledProgressBarItemContainer>
))}
</StyledProgressBarContainer>
</StyledContainer>
);
}

View File

@ -0,0 +1,7 @@
import { useContext } from 'react';
import { EntityIdContext } from '../states/EntityIdContext';
export function useCurrentEntityId() {
return useContext(EntityIdContext);
}

View File

@ -0,0 +1,246 @@
import { useContext } from 'react';
import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip';
import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
import { isViewFieldChipValue } from '../types/guards/isViewFieldChipValue';
import { isViewFieldDate } from '../types/guards/isViewFieldDate';
import { isViewFieldDateValue } from '../types/guards/isViewFieldDateValue';
import { isViewFieldDoubleText } from '../types/guards/isViewFieldDoubleText';
import { isViewFieldDoubleTextChip } from '../types/guards/isViewFieldDoubleTextChip';
import { isViewFieldDoubleTextChipValue } from '../types/guards/isViewFieldDoubleTextChipValue';
import { isViewFieldDoubleTextValue } from '../types/guards/isViewFieldDoubleTextValue';
import { isViewFieldNumber } from '../types/guards/isViewFieldNumber';
import { isViewFieldNumberValue } from '../types/guards/isViewFieldNumberValue';
import { isViewFieldPhone } from '../types/guards/isViewFieldPhone';
import { isViewFieldPhoneValue } from '../types/guards/isViewFieldPhoneValue';
import { isViewFieldProbability } from '../types/guards/isViewFieldProbability';
import { isViewFieldProbabilityValue } from '../types/guards/isViewFieldProbabilityValue';
import { isViewFieldRelation } from '../types/guards/isViewFieldRelation';
import { isViewFieldRelationValue } from '../types/guards/isViewFieldRelationValue';
import { isViewFieldText } from '../types/guards/isViewFieldText';
import { isViewFieldTextValue } from '../types/guards/isViewFieldTextValue';
import { isViewFieldURL } from '../types/guards/isViewFieldURL';
import { isViewFieldURLValue } from '../types/guards/isViewFieldURLValue';
import {
ViewFieldChipMetadata,
ViewFieldChipValue,
ViewFieldDateMetadata,
ViewFieldDateValue,
ViewFieldDefinition,
ViewFieldDoubleTextChipMetadata,
ViewFieldDoubleTextChipValue,
ViewFieldDoubleTextMetadata,
ViewFieldDoubleTextValue,
ViewFieldMetadata,
ViewFieldNumberMetadata,
ViewFieldNumberValue,
ViewFieldPhoneMetadata,
ViewFieldPhoneValue,
ViewFieldProbabilityMetadata,
ViewFieldProbabilityValue,
ViewFieldRelationMetadata,
ViewFieldRelationValue,
ViewFieldTextMetadata,
ViewFieldTextValue,
ViewFieldURLMetadata,
ViewFieldURLValue,
} from '../types/ViewField';
export function useUpdateGenericEntityField() {
const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);
const [updateEntity] = useUpdateEntityMutation();
return function updatePeopleField<
MetadataType extends ViewFieldMetadata,
ValueType extends MetadataType extends ViewFieldDoubleTextMetadata
? ViewFieldDoubleTextValue
: MetadataType extends ViewFieldTextMetadata
? ViewFieldTextValue
: MetadataType extends ViewFieldPhoneMetadata
? ViewFieldPhoneValue
: MetadataType extends ViewFieldURLMetadata
? ViewFieldURLValue
: MetadataType extends ViewFieldNumberMetadata
? ViewFieldNumberValue
: MetadataType extends ViewFieldDateMetadata
? ViewFieldDateValue
: MetadataType extends ViewFieldChipMetadata
? ViewFieldChipValue
: MetadataType extends ViewFieldDoubleTextChipMetadata
? ViewFieldDoubleTextChipValue
: MetadataType extends ViewFieldRelationMetadata
? ViewFieldRelationValue
: MetadataType extends ViewFieldProbabilityMetadata
? ViewFieldProbabilityValue
: unknown,
>(
currentEntityId: string,
viewField: ViewFieldDefinition<MetadataType>,
newFieldValue: ValueType,
) {
const newFieldValueUnknown = newFieldValue as unknown;
// TODO: improve type guards organization, maybe with a common typeguard for all view fields
// taking an object of options as parameter ?
//
// The goal would be to check that the view field value not only is valid,
// but also that it is validated against the corresponding view field type
// Relation
if (
isViewFieldRelation(viewField) &&
isViewFieldRelationValue(newFieldValueUnknown)
) {
const newSelectedEntity = newFieldValueUnknown;
const fieldName = viewField.metadata.fieldName;
if (!newSelectedEntity) {
updateEntity({
variables: {
where: { id: currentEntityId },
data: {
[fieldName]: {
disconnect: true,
},
},
},
});
} else {
updateEntity({
variables: {
where: { id: currentEntityId },
data: {
[fieldName]: {
connect: { id: newSelectedEntity.id },
},
},
},
});
}
// Chip
} else if (
isViewFieldChip(viewField) &&
isViewFieldChipValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.contentFieldName]: newContent },
},
});
// Text
} else if (
isViewFieldText(viewField) &&
isViewFieldTextValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.fieldName]: newContent },
},
});
// Double text
} else if (
isViewFieldDoubleText(viewField) &&
isViewFieldDoubleTextValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: {
[viewField.metadata.firstValueFieldName]: newContent.firstValue,
[viewField.metadata.secondValueFieldName]: newContent.secondValue,
},
},
});
// Double Text Chip
} else if (
isViewFieldDoubleTextChip(viewField) &&
isViewFieldDoubleTextChipValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: {
[viewField.metadata.firstValueFieldName]: newContent.firstValue,
[viewField.metadata.secondValueFieldName]: newContent.secondValue,
},
},
});
// Phone
} else if (
isViewFieldPhone(viewField) &&
isViewFieldPhoneValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.fieldName]: newContent },
},
});
// URL
} else if (
isViewFieldURL(viewField) &&
isViewFieldURLValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.fieldName]: newContent },
},
});
// Number
} else if (
isViewFieldNumber(viewField) &&
isViewFieldNumberValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.fieldName]: newContent },
},
});
// Date
} else if (
isViewFieldDate(viewField) &&
isViewFieldDateValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.fieldName]: newContent },
},
});
} else if (
isViewFieldProbability(viewField) &&
isViewFieldProbabilityValue(newFieldValueUnknown)
) {
const newContent = newFieldValueUnknown;
updateEntity({
variables: {
where: { id: currentEntityId },
data: { [viewField.metadata.fieldName]: newContent },
},
});
}
};
}

View File

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const EditableFieldContext = createContext<string | null>(null);

View File

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const EntityIdContext = createContext<string | null>(null);

View File

@ -0,0 +1,9 @@
import { atomFamily } from 'recoil';
export const genericEntitiesFamilyState = atomFamily<
Record<string, unknown> | null,
string
>({
key: 'genericEntitiesFamilyState',
default: null,
});

View File

@ -0,0 +1,18 @@
import { selectorFamily } from 'recoil';
import { genericEntitiesFamilyState } from './genericEntitiesFamilyState';
export const genericEntityFieldFamilySelector = selectorFamily({
key: 'genericEntityFieldFamilySelector',
get:
<T>({ fieldName, entityId }: { fieldName: string; entityId: string }) =>
({ get }) =>
get(genericEntitiesFamilyState(entityId))?.[fieldName] as T,
set:
<T>({ fieldName, entityId }: { fieldName: string; entityId: string }) =>
({ set }, newValue: T) =>
set(genericEntitiesFamilyState(entityId), (prevState) => ({
...prevState,
[fieldName]: newValue,
})),
});

View File

@ -10,7 +10,8 @@ export type ViewFieldType =
| 'number' | 'number'
| 'date' | 'date'
| 'phone' | 'phone'
| 'url'; | 'url'
| 'probability';
export type ViewFieldTextMetadata = { export type ViewFieldTextMetadata = {
type: 'text'; type: 'text';
@ -72,6 +73,11 @@ export type ViewFieldDoubleTextChipMetadata = {
entityType: Entity; entityType: Entity;
}; };
export type ViewFieldProbabilityMetadata = {
type: 'probability';
fieldName: string;
};
export type ViewFieldMetadata = { type: ViewFieldType } & ( export type ViewFieldMetadata = { type: ViewFieldType } & (
| ViewFieldTextMetadata | ViewFieldTextMetadata
| ViewFieldRelationMetadata | ViewFieldRelationMetadata
@ -82,6 +88,7 @@ export type ViewFieldMetadata = { type: ViewFieldType } & (
| ViewFieldURLMetadata | ViewFieldURLMetadata
| ViewFieldNumberMetadata | ViewFieldNumberMetadata
| ViewFieldDateMetadata | ViewFieldDateMetadata
| ViewFieldProbabilityMetadata
); );
export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = { export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
@ -101,7 +108,8 @@ export type ViewFieldChipValue = string;
export type ViewFieldDateValue = string; export type ViewFieldDateValue = string;
export type ViewFieldPhoneValue = string; export type ViewFieldPhoneValue = string;
export type ViewFieldURLValue = string; export type ViewFieldURLValue = string;
export type ViewFieldNumberValue = number; export type ViewFieldNumberValue = number | null;
export type ViewFieldProbabilityValue = number;
export type ViewFieldDoubleTextValue = { export type ViewFieldDoubleTextValue = {
firstValue: string; firstValue: string;

View File

@ -0,0 +1,11 @@
import {
ViewFieldDefinition,
ViewFieldMetadata,
ViewFieldProbabilityMetadata,
} from '../ViewField';
export function isViewFieldProbability(
field: ViewFieldDefinition<ViewFieldMetadata>,
): field is ViewFieldDefinition<ViewFieldProbabilityMetadata> {
return field.metadata.type === 'probability';
}

View File

@ -0,0 +1,12 @@
import { ViewFieldProbabilityValue } from '../ViewField';
// TODO: add yup
export function isViewFieldProbabilityValue(
fieldValue: unknown,
): fieldValue is ViewFieldProbabilityValue {
return (
fieldValue !== null &&
fieldValue !== undefined &&
typeof fieldValue === 'number'
);
}

View File

@ -1,5 +1,3 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField'; import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext'; import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay'; import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
@ -16,49 +14,29 @@ type OwnProps = {
}; };
export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) { export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
async function handleChange(newValue: string) { async function handleChange(newValue: string) {
setInternalValue(newValue);
onSubmit?.(newValue); onSubmit?.(newValue);
} }
async function handleSubmit() { const internalDateValue = value ? parseDate(value).toJSDate() : null;
if (!internalValue) return;
onSubmit?.(internalValue);
}
async function handleCancel() {
setInternalValue(value);
}
const internalDateValue = internalValue
? parseDate(internalValue).toJSDate()
: null;
return ( return (
<RecoilScope SpecificContext={FieldContext}> <RecoilScope SpecificContext={FieldContext}>
<EditableField <EditableField
onSubmit={handleSubmit} // onSubmit={handleSubmit}
onCancel={handleCancel} // onCancel={handleCancel}
iconLabel={icon} iconLabel={icon}
label={label} label={label}
editModeContent={ editModeContent={
<EditableFieldEditModeDate <EditableFieldEditModeDate
value={internalValue || new Date().toISOString()} value={value || new Date().toISOString()}
onChange={(newValue: string) => { onChange={(newValue: string) => {
handleChange(newValue); handleChange(newValue);
}} }}
/> />
} }
displayModeContent={<DateInputDisplay value={internalDateValue} />} displayModeContent={<DateInputDisplay value={internalDateValue} />}
isDisplayModeContentEmpty={!internalValue} isDisplayModeContentEmpty={!value}
/> />
</RecoilScope> </RecoilScope>
); );

View File

@ -1,3 +1,5 @@
import { useEffect, useState } from 'react';
import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit'; import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { parseDate } from '~/utils/date-utils'; import { parseDate } from '~/utils/date-utils';
@ -11,6 +13,12 @@ type OwnProps = {
}; };
export function EditableFieldEditModeDate({ value, onChange }: OwnProps) { export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
useEffect(() => {
setInternalValue(value);
}, [value]);
const { closeEditableField } = useEditableField(); const { closeEditableField } = useEditableField();
function handleChange(newValue: string) { function handleChange(newValue: string) {
@ -20,7 +28,7 @@ export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
return ( return (
<DateInputEdit <DateInputEdit
value={parseDate(value).toJSDate()} value={internalValue ? parseDate(internalValue).toJSDate() : new Date()}
onChange={(newDate: Date) => { onChange={(newDate: Date) => {
handleChange(newDate.toISOString()); handleChange(newDate.toISOString());
}} }}

View File

@ -1,73 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '~/utils/cast-as-integer-or-null';
type OwnProps = {
icon?: React.ReactNode;
placeholder?: string;
value: number | null | undefined;
onSubmit?: (newValue: number | null) => void;
};
export function NumberEditableField({
icon,
placeholder,
value,
onSubmit,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value?.toString());
useEffect(() => {
setInternalValue(value?.toString());
}, [value]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
if (!canBeCastAsIntegerOrNull(internalValue)) {
handleCancel();
return;
}
const valueCastedAsNumberOrNull = castAsIntegerOrNull(internalValue);
onSubmit?.(valueCastedAsNumberOrNull);
setInternalValue(valueCastedAsNumberOrNull?.toString());
}
async function handleCancel() {
setInternalValue(value?.toString());
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={icon}
editModeContent={
<TextInputEdit
placeholder={placeholder ?? ''}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue}
isDisplayModeContentEmpty={!(internalValue !== '' && internalValue)}
/>
</RecoilScope>
);
}

View File

@ -1,32 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconCurrencyDollar } from '@tabler/icons-react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { NumberEditableField } from '../NumberEditableField';
const meta: Meta<typeof NumberEditableField> = {
title: 'UI/EditableField/NumberEditableField',
component: NumberEditableField,
decorators: [ComponentDecorator],
argTypes: {
icon: {
type: 'boolean',
mapping: {
true: <IconCurrencyDollar />,
false: undefined,
},
},
value: { control: { type: 'number' } },
},
args: {
value: 10,
icon: true,
placeholder: 'Number',
},
};
export default meta;
type Story = StoryObj<typeof NumberEditableField>;
export const Default: Story = {};

View File

@ -0,0 +1,5 @@
import { createContext } from 'react';
import { FilterDefinition } from '../types/FilterDefinition';
export const AvailableFiltersContext = createContext<FilterDefinition[]>([]);

View File

@ -12,7 +12,7 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
import type { import type {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldMetadata, ViewFieldMetadata,
} from '../types/ViewField'; } from '../../editable-field/types/ViewField';
const StyledColumnMenu = styled(DropdownMenu)` const StyledColumnMenu = styled(DropdownMenu)`
font-weight: ${({ theme }) => theme.font.weight.regular}; font-weight: ${({ theme }) => theme.font.weight.regular};

View File

@ -13,6 +13,10 @@ import {
useUpdateViewFieldMutation, useUpdateViewFieldMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '../../editable-field/types/ViewField';
import { toViewFieldInput } from '../hooks/useLoadView'; import { toViewFieldInput } from '../hooks/useLoadView';
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState'; import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
import { import {
@ -21,10 +25,6 @@ import {
viewFieldsState, viewFieldsState,
visibleViewFieldsState, visibleViewFieldsState,
} from '../states/viewFieldsState'; } from '../states/viewFieldsState';
import type {
ViewFieldDefinition,
ViewFieldMetadata,
} from '../types/ViewField';
import { ColumnHead } from './ColumnHead'; import { ColumnHead } from './ColumnHead';
import { EntityTableColumnMenu } from './EntityTableColumnMenu'; import { EntityTableColumnMenu } from './EntityTableColumnMenu';

View File

@ -1,10 +1,10 @@
import { defaultOrderBy } from '@/people/queries'; import { defaultOrderBy } from '@/people/queries';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldMetadata, ViewFieldMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { useLoadView } from '../hooks/useLoadView'; import { useLoadView } from '../hooks/useLoadView';

View File

@ -1,17 +1,17 @@
import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate';
import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText';
import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip';
import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber';
import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone';
import { isViewFieldRelation } from '@/ui/editable-field/types/guards/isViewFieldRelation';
import { isViewFieldText } from '@/ui/editable-field/types/guards/isViewFieldText';
import { isViewFieldURL } from '@/ui/editable-field/types/guards/isViewFieldURL';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldMetadata, ViewFieldMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { isViewFieldChip } from '../../types/guards/isViewFieldChip'; import { isViewFieldChip } from '../../../editable-field/types/guards/isViewFieldChip';
import { isViewFieldDate } from '../../types/guards/isViewFieldDate';
import { isViewFieldDoubleText } from '../../types/guards/isViewFieldDoubleText';
import { isViewFieldDoubleTextChip } from '../../types/guards/isViewFieldDoubleTextChip';
import { isViewFieldNumber } from '../../types/guards/isViewFieldNumber';
import { isViewFieldPhone } from '../../types/guards/isViewFieldPhone';
import { isViewFieldRelation } from '../../types/guards/isViewFieldRelation';
import { isViewFieldText } from '../../types/guards/isViewFieldText';
import { isViewFieldURL } from '../../types/guards/isViewFieldURL';
import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell'; import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell';
import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell'; import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell';
import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell'; import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell';

View File

@ -1,8 +1,8 @@
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { import {
ViewFieldChipMetadata, ViewFieldChipMetadata,
ViewFieldDefinition, ViewFieldDefinition,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode'; import { GenericEditableChipCellDisplayMode } from './GenericEditableChipCellDisplayMode';
import { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode'; import { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode';

View File

@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { CompanyChip } from '@/companies/components/CompanyChip'; import { CompanyChip } from '@/companies/components/CompanyChip';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldChipMetadata, ViewFieldChipMetadata,
ViewFieldDefinition, ViewFieldDefinition,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
type OwnProps = { type OwnProps = {

View File

@ -1,12 +1,12 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldChipMetadata, ViewFieldChipMetadata,
ViewFieldDefinition, ViewFieldDefinition,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { TextCellEdit } from './TextCellEdit'; import { TextCellEdit } from './TextCellEdit';

View File

@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
} from '@/ui/editable-field/types/ViewField';
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay'; import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import {
ViewFieldDateMetadata,
ViewFieldDefinition,
} from '@/ui/table/types/ViewField';
import { GenericEditableDateCellEditMode } from './GenericEditableDateCellEditMode'; import { GenericEditableDateCellEditMode } from './GenericEditableDateCellEditMode';

View File

@ -1,13 +1,13 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDateMetadata, ViewFieldDateMetadata,
ViewFieldDefinition, ViewFieldDefinition,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { DateCellEdit } from './DateCellEdit'; import { DateCellEdit } from './DateCellEdit';

View File

@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import {
ViewFieldDefinition,
ViewFieldDoubleTextMetadata,
} from '@/ui/editable-field/types/ViewField';
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay'; import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import {
ViewFieldDefinition,
ViewFieldDoubleTextMetadata,
} from '@/ui/table/types/ViewField';
import { GenericEditableDoubleTextCellEditMode } from './GenericEditableDoubleTextCellEditMode'; import { GenericEditableDoubleTextCellEditMode } from './GenericEditableDoubleTextCellEditMode';

View File

@ -1,12 +1,12 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldDoubleTextMetadata, ViewFieldDoubleTextMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { DoubleTextCellEdit } from './DoubleTextCellEdit'; import { DoubleTextCellEdit } from './DoubleTextCellEdit';

View File

@ -1,9 +1,9 @@
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldDoubleTextChipMetadata, ViewFieldDoubleTextChipMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { GenericEditableDoubleTextChipCellDisplayMode } from './GenericEditableDoubleTextChipCellDisplayMode'; import { GenericEditableDoubleTextChipCellDisplayMode } from './GenericEditableDoubleTextChipCellDisplayMode';
import { GenericEditableDoubleTextChipCellEditMode } from './GenericEditableDoubleTextChipCellEditMode'; import { GenericEditableDoubleTextChipCellEditMode } from './GenericEditableDoubleTextChipCellEditMode';

View File

@ -2,13 +2,13 @@ import { useRecoilState } from 'recoil';
import { CompanyChip } from '@/companies/components/CompanyChip'; import { CompanyChip } from '@/companies/components/CompanyChip';
import { PersonChip } from '@/people/components/PersonChip'; import { PersonChip } from '@/people/components/PersonChip';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldDoubleTextChipMetadata, ViewFieldDoubleTextChipMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
type OwnProps = { type OwnProps = {
viewField: ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>; viewField: ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>;

View File

@ -1,12 +1,12 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldDoubleTextChipMetadata, ViewFieldDoubleTextChipMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { DoubleTextCellEdit } from './DoubleTextCellEdit'; import { DoubleTextCellEdit } from './DoubleTextCellEdit';

View File

@ -1,12 +1,12 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldNumberMetadata, ViewFieldNumberMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { GenericEditableNumberCellEditMode } from './GenericEditableNumberCellEditMode'; import { GenericEditableNumberCellEditMode } from './GenericEditableNumberCellEditMode';

View File

@ -1,12 +1,12 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldNumberMetadata, ViewFieldNumberMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { TextCellEdit } from './TextCellEdit'; import { TextCellEdit } from './TextCellEdit';

View File

@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import {
ViewFieldDefinition,
ViewFieldPhoneMetadata,
} from '@/ui/editable-field/types/ViewField';
import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay'; import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import {
ViewFieldDefinition,
ViewFieldPhoneMetadata,
} from '@/ui/table/types/ViewField';
import { GenericEditablePhoneCellEditMode } from './GenericEditablePhoneCellEditMode'; import { GenericEditablePhoneCellEditMode } from './GenericEditablePhoneCellEditMode';

View File

@ -1,12 +1,12 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldPhoneMetadata, ViewFieldPhoneMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { TextCellEdit } from './TextCellEdit'; import { TextCellEdit } from './TextCellEdit';

View File

@ -1,9 +1,9 @@
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldRelationMetadata, ViewFieldRelationMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode'; import { GenericEditableRelationCellDisplayMode } from './GenericEditableRelationCellDisplayMode';
import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode'; import { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode';

View File

@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { CompanyChip } from '@/companies/components/CompanyChip'; import { CompanyChip } from '@/companies/components/CompanyChip';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldRelationMetadata, ViewFieldRelationMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { UserChip } from '@/users/components/UserChip'; import { UserChip } from '@/users/components/UserChip';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';

View File

@ -1,16 +1,16 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { CompanyPickerCell } from '@/companies/components/CompanyPickerCell'; import { CompanyPickerCell } from '@/companies/components/CompanyPickerCell';
import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
} from '@/ui/editable-field/types/ViewField';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell'; import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField'; import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import {
ViewFieldDefinition,
ViewFieldRelationMetadata,
} from '@/ui/table/types/ViewField';
import { UserPicker } from '@/users/components/UserPicker'; import { UserPicker } from '@/users/components/UserPicker';
type OwnProps = { type OwnProps = {

View File

@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import {
ViewFieldDefinition,
ViewFieldTextMetadata,
} from '@/ui/editable-field/types/ViewField';
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay'; import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import {
ViewFieldDefinition,
ViewFieldTextMetadata,
} from '@/ui/table/types/ViewField';
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode'; import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';

View File

@ -1,12 +1,12 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldTextMetadata, ViewFieldTextMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { TextCellEdit } from './TextCellEdit'; import { TextCellEdit } from './TextCellEdit';

View File

@ -1,13 +1,13 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import {
ViewFieldDefinition,
ViewFieldURLMetadata,
} from '@/ui/editable-field/types/ViewField';
import { InplaceInputURLDisplayMode } from '@/ui/input/url/components/URLTextInputDisplay'; import { InplaceInputURLDisplayMode } from '@/ui/input/url/components/URLTextInputDisplay';
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell'; import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId'; import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector'; import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import {
ViewFieldDefinition,
ViewFieldURLMetadata,
} from '@/ui/table/types/ViewField';
import { sanitizeURL } from '~/utils'; import { sanitizeURL } from '~/utils';
import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode'; import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode';

View File

@ -1,12 +1,12 @@
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldURLMetadata, ViewFieldURLMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
import { TextCellEdit } from './TextCellEdit'; import { TextCellEdit } from './TextCellEdit';

View File

@ -8,13 +8,13 @@ import {
useGetViewFieldsQuery, useGetViewFieldsQuery,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
import { viewFieldsState } from '../states/viewFieldsState';
import type { import type {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldMetadata, ViewFieldMetadata,
ViewFieldTextMetadata, ViewFieldTextMetadata,
} from '../types/ViewField'; } from '../../editable-field/types/ViewField';
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
import { viewFieldsState } from '../states/viewFieldsState';
const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = { const DEFAULT_VIEW_FIELD_METADATA: ViewFieldTextMetadata = {
type: 'text', type: 'text',

View File

@ -1,25 +1,25 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip';
import { isViewFieldDate } from '@/ui/editable-field/types/guards/isViewFieldDate';
import { isViewFieldDateValue } from '@/ui/editable-field/types/guards/isViewFieldDateValue';
import { isViewFieldDoubleText } from '@/ui/editable-field/types/guards/isViewFieldDoubleText';
import { isViewFieldDoubleTextChip } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChip';
import { isViewFieldDoubleTextChipValue } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextChipValue';
import { isViewFieldDoubleTextValue } from '@/ui/editable-field/types/guards/isViewFieldDoubleTextValue';
import { isViewFieldNumber } from '@/ui/editable-field/types/guards/isViewFieldNumber';
import { isViewFieldNumberValue } from '@/ui/editable-field/types/guards/isViewFieldNumberValue';
import { isViewFieldPhone } from '@/ui/editable-field/types/guards/isViewFieldPhone';
import { isViewFieldPhoneValue } from '@/ui/editable-field/types/guards/isViewFieldPhoneValue';
import { isViewFieldRelation } from '@/ui/editable-field/types/guards/isViewFieldRelation';
import { isViewFieldRelationValue } from '@/ui/editable-field/types/guards/isViewFieldRelationValue';
import { isViewFieldText } from '@/ui/editable-field/types/guards/isViewFieldText';
import { isViewFieldTextValue } from '@/ui/editable-field/types/guards/isViewFieldTextValue';
import { isViewFieldURL } from '@/ui/editable-field/types/guards/isViewFieldURL';
import { isViewFieldURLValue } from '@/ui/editable-field/types/guards/isViewFieldURLValue';
import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext'; import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
import { isViewFieldChip } from '@/ui/table/types/guards/isViewFieldChip';
import { isViewFieldRelation } from '@/ui/table/types/guards/isViewFieldRelation';
import { isViewFieldText } from '@/ui/table/types/guards/isViewFieldText';
import { isViewFieldChipValue } from '../types/guards/isViewFieldChipValue'; import { isViewFieldChipValue } from '../../editable-field/types/guards/isViewFieldChipValue';
import { isViewFieldDate } from '../types/guards/isViewFieldDate';
import { isViewFieldDateValue } from '../types/guards/isViewFieldDateValue';
import { isViewFieldDoubleText } from '../types/guards/isViewFieldDoubleText';
import { isViewFieldDoubleTextChip } from '../types/guards/isViewFieldDoubleTextChip';
import { isViewFieldDoubleTextChipValue } from '../types/guards/isViewFieldDoubleTextChipValue';
import { isViewFieldDoubleTextValue } from '../types/guards/isViewFieldDoubleTextValue';
import { isViewFieldNumber } from '../types/guards/isViewFieldNumber';
import { isViewFieldNumberValue } from '../types/guards/isViewFieldNumberValue';
import { isViewFieldPhone } from '../types/guards/isViewFieldPhone';
import { isViewFieldPhoneValue } from '../types/guards/isViewFieldPhoneValue';
import { isViewFieldRelationValue } from '../types/guards/isViewFieldRelationValue';
import { isViewFieldTextValue } from '../types/guards/isViewFieldTextValue';
import { isViewFieldURL } from '../types/guards/isViewFieldURL';
import { isViewFieldURLValue } from '../types/guards/isViewFieldURLValue';
import { import {
ViewFieldChipMetadata, ViewFieldChipMetadata,
ViewFieldChipValue, ViewFieldChipValue,
@ -41,7 +41,7 @@ import {
ViewFieldTextValue, ViewFieldTextValue,
ViewFieldURLMetadata, ViewFieldURLMetadata,
ViewFieldURLValue, ViewFieldURLValue,
} from '../types/ViewField'; } from '../../editable-field/types/ViewField';
export function useUpdateEntityField() { export function useUpdateEntityField() {
const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext); const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);

View File

@ -1,6 +1,9 @@
import { createContext } from 'react'; import { createContext } from 'react';
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField'; import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '../../editable-field/types/ViewField';
export const ViewFieldContext = export const ViewFieldContext =
createContext<ViewFieldDefinition<ViewFieldMetadata> | null>(null); createContext<ViewFieldDefinition<ViewFieldMetadata> | null>(null);

View File

@ -6,7 +6,7 @@ import { peopleViewFields } from '@/people/constants/peopleViewFields';
import type { import type {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldMetadata, ViewFieldMetadata,
} from '../types/ViewField'; } from '../../editable-field/types/ViewField';
export const viewFieldsState = atom<{ export const viewFieldsState = atom<{
objectName: 'company' | 'person' | ''; objectName: 'company' | 'person' | '';

View File

@ -8,6 +8,10 @@ import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem'; import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/editable-field/types/ViewField';
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton'; import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope'; import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon'; import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon';
@ -15,10 +19,6 @@ import {
hiddenViewFieldsState, hiddenViewFieldsState,
visibleViewFieldsState, visibleViewFieldsState,
} from '@/ui/table/states/viewFieldsState'; } from '@/ui/table/states/viewFieldsState';
import {
ViewFieldDefinition,
ViewFieldMetadata,
} from '@/ui/table/types/ViewField';
import { useUpdateViewFieldMutation } from '~/generated/graphql'; import { useUpdateViewFieldMutation } from '~/generated/graphql';
import { GET_VIEW_FIELDS } from '../queries/select'; import { GET_VIEW_FIELDS } from '../queries/select';

View File

@ -10,7 +10,7 @@ import { DropdownMenuSubheader } from '@/ui/dropdown/components/DropdownMenuSubh
import { import {
ViewFieldDefinition, ViewFieldDefinition,
ViewFieldMetadata, ViewFieldMetadata,
} from '@/ui/table/types/ViewField'; } from '@/ui/editable-field/types/ViewField';
type OptionsDropdownSectionProps = { type OptionsDropdownSectionProps = {
renderActions: ( renderActions: (

View File

@ -14,6 +14,7 @@ import { EntityBoard } from '@/ui/board/components/EntityBoard';
import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar'; import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar';
import { BoardOptionsContext } from '@/ui/board/states/BoardOptionsContext'; import { BoardOptionsContext } from '@/ui/board/states/BoardOptionsContext';
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers'; import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
import { AvailableFiltersContext } from '@/ui/filter-n-sort/states/AvailableFiltersContext';
import { IconTargetArrow } from '@/ui/icon/index'; import { IconTargetArrow } from '@/ui/icon/index';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer'; import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
@ -83,19 +84,20 @@ export function Opportunities() {
> >
<BoardOptionsContext.Provider value={opportunitiesBoardOptions}> <BoardOptionsContext.Provider value={opportunitiesBoardOptions}>
<RecoilScope SpecificContext={CompanyBoardContext}> <RecoilScope SpecificContext={CompanyBoardContext}>
<HooksCompanyBoard <AvailableFiltersContext.Provider
availableFilters={opportunitiesBoardOptions.filters} value={opportunitiesBoardOptions.filters}
orderBy={orderBy} >
/> <HooksCompanyBoard orderBy={orderBy} />
<EntityBoard <EntityBoard
boardOptions={opportunitiesBoardOptions} boardOptions={opportunitiesBoardOptions}
updateSorts={updateSorts} updateSorts={updateSorts}
onEditColumnColor={handleEditColumnColor} onEditColumnColor={handleEditColumnColor}
onEditColumnTitle={handleEditColumnTitle} onEditColumnTitle={handleEditColumnTitle}
/> />
<EntityBoardActionBar> <EntityBoardActionBar>
<BoardActionBarButtonDeleteBoardCard onDelete={handleDelete} /> <BoardActionBarButtonDeleteBoardCard onDelete={handleDelete} />
</EntityBoardActionBar> </EntityBoardActionBar>
</AvailableFiltersContext.Provider>
</RecoilScope> </RecoilScope>
</BoardOptionsContext.Provider> </BoardOptionsContext.Provider>
</WithTopBarContainer> </WithTopBarContainer>

View File

@ -129,11 +129,13 @@ export function AuthAutoRouter() {
} }
} }
eventTracker('pageview', { setTimeout(() => {
location: { eventTracker('pageview', {
pathname: location.pathname, location: {
}, pathname: location.pathname,
}); },
});
}, 500);
}, [ }, [
onboardingStatus, onboardingStatus,
navigate, navigate,

Some files were not shown because too many files have changed in this diff Show More