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:
@ -23,6 +23,7 @@
|
||||
"apollo-upload-client": "^17.0.0",
|
||||
"cmdk": "^0.2.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"deep-equal": "^2.2.2",
|
||||
"framer-motion": "^10.12.17",
|
||||
"graphql": "^16.6.0",
|
||||
"hex-rgb": "^5.0.0",
|
||||
@ -124,6 +125,7 @@
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/apollo-upload-client": "^17.0.2",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
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 NavTitle from '@/ui/navbar/components/NavTitle';
|
||||
|
||||
import { measureTotalFrameLoad } from './utils/measureTotalFrameLoad';
|
||||
|
||||
export function AppNavbar() {
|
||||
const theme = useTheme();
|
||||
const currentPath = useLocation().pathname;
|
||||
const { openCommandMenu } = useCommandMenu();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isInSubMenu = useIsSubMenuNavbarDisplayed();
|
||||
|
||||
return (
|
||||
@ -62,12 +66,22 @@ export function AppNavbar() {
|
||||
<NavItem
|
||||
label="People"
|
||||
to="/people"
|
||||
onClick={() => {
|
||||
measureTotalFrameLoad('people');
|
||||
|
||||
navigate('/people');
|
||||
}}
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
active={currentPath === '/people'}
|
||||
/>
|
||||
<NavItem
|
||||
label="Opportunities"
|
||||
to="/opportunities"
|
||||
// to="/opportunities"
|
||||
onClick={() => {
|
||||
measureTotalFrameLoad('opportunities');
|
||||
|
||||
navigate('/opportunities');
|
||||
}}
|
||||
icon={<IconTargetArrow size={theme.icon.size.md} />}
|
||||
active={currentPath === '/opportunities'}
|
||||
/>
|
||||
|
||||
@ -1942,7 +1942,7 @@ export type User = {
|
||||
phoneNumber?: Maybe<Scalars['String']>;
|
||||
settings: UserSettings;
|
||||
settingsId: Scalars['String'];
|
||||
supportUserHash: Scalars['String'];
|
||||
supportUserHash?: Maybe<Scalars['String']>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
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<{
|
||||
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; }>;
|
||||
|
||||
@ -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 UpdateOnePipelineProgressMutationVariables = Exact<{
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
amount?: InputMaybe<Scalars['Int']>;
|
||||
closeDate?: InputMaybe<Scalars['DateTime']>;
|
||||
probability?: InputMaybe<Scalars['Int']>;
|
||||
pointOfContactId?: InputMaybe<Scalars['String']>;
|
||||
data: PipelineProgressUpdateInput;
|
||||
where: PipelineProgressWhereUniqueInput;
|
||||
}>;
|
||||
|
||||
|
||||
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<{
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
@ -2685,7 +2682,7 @@ export type SearchActivityQuery = { __typename?: 'Query', searchResults: Array<{
|
||||
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; }>;
|
||||
|
||||
@ -4564,14 +4561,12 @@ export type UpdatePipelineStageMutationHookResult = ReturnType<typeof useUpdateP
|
||||
export type UpdatePipelineStageMutationResult = Apollo.MutationResult<UpdatePipelineStageMutation>;
|
||||
export type UpdatePipelineStageMutationOptions = Apollo.BaseMutationOptions<UpdatePipelineStageMutation, UpdatePipelineStageMutationVariables>;
|
||||
export const UpdateOnePipelineProgressDocument = gql`
|
||||
mutation UpdateOnePipelineProgress($id: String, $amount: Int, $closeDate: DateTime, $probability: Int, $pointOfContactId: String) {
|
||||
updateOnePipelineProgress(
|
||||
where: {id: $id}
|
||||
data: {amount: $amount, closeDate: $closeDate, probability: $probability, pointOfContact: {connect: {id: $pointOfContactId}}}
|
||||
) {
|
||||
mutation UpdateOnePipelineProgress($data: PipelineProgressUpdateInput!, $where: PipelineProgressWhereUniqueInput!) {
|
||||
updateOnePipelineProgress(where: $where, data: $data) {
|
||||
id
|
||||
amount
|
||||
closeDate
|
||||
probability
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -4590,11 +4585,8 @@ export type UpdateOnePipelineProgressMutationFn = Apollo.MutationFunction<Update
|
||||
* @example
|
||||
* const [updateOnePipelineProgressMutation, { data, loading, error }] = useUpdateOnePipelineProgressMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* amount: // value for 'amount'
|
||||
* closeDate: // value for 'closeDate'
|
||||
* probability: // value for 'probability'
|
||||
* pointOfContactId: // value for 'pointOfContactId'
|
||||
* data: // value for 'data'
|
||||
* where: // value for 'where'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
|
||||
@ -17,10 +17,7 @@ const meta: Meta<typeof EntityBoard> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilScope SpecificContext={CompanyBoardContext}>
|
||||
<HooksCompanyBoard
|
||||
availableFilters={[]}
|
||||
orderBy={defaultPipelineProgressOrderBy}
|
||||
/>
|
||||
<HooksCompanyBoard orderBy={defaultPipelineProgressOrderBy} />
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
|
||||
@ -19,10 +19,7 @@ const meta: Meta<typeof CompanyBoardCard> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilScope SpecificContext={CompanyBoardContext}>
|
||||
<HooksCompanyBoard
|
||||
availableFilters={[]}
|
||||
orderBy={defaultPipelineProgressOrderBy}
|
||||
/>
|
||||
<HooksCompanyBoard orderBy={defaultPipelineProgressOrderBy} />
|
||||
<RecoilScope SpecificContext={BoardColumnContext}>
|
||||
<BoardCardIdContext.Provider value={mockedPipelineProgressData[1].id}>
|
||||
<MemoryRouter>
|
||||
|
||||
@ -1,27 +1,21 @@
|
||||
import { ReactNode, useCallback, useContext } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { ReactNode, useContext } from 'react';
|
||||
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 { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState';
|
||||
import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState';
|
||||
import { EntityChipVariant } from '@/ui/chip/components/EntityChip';
|
||||
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
|
||||
import { NumberEditableField } from '@/ui/editable-field/variants/components/NumberEditableField';
|
||||
import { IconCurrencyDollar, IconProgressCheck } from '@/ui/icon';
|
||||
import { IconCalendarEvent } from '@/ui/icon';
|
||||
import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField';
|
||||
import {
|
||||
Checkbox,
|
||||
CheckboxVariant,
|
||||
} from '@/ui/input/checkbox/components/Checkbox';
|
||||
import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext';
|
||||
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
|
||||
import { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
import { PipelineProgressForBoard } from '../types/CompanyProgress';
|
||||
import { companyProgressesFamilyState } from '../states/companyProgressesFamilyState';
|
||||
|
||||
import { CompanyChip } from './CompanyChip';
|
||||
|
||||
@ -106,8 +100,6 @@ const StyledFieldContainer = styled.div`
|
||||
`;
|
||||
|
||||
export function CompanyBoardCard() {
|
||||
const [updatePipelineProgress] = useUpdateOnePipelineProgressMutation();
|
||||
|
||||
const boardCardId = useContext(BoardCardIdContext);
|
||||
|
||||
const [companyProgress] = useRecoilState(
|
||||
@ -118,6 +110,7 @@ export function CompanyBoardCard() {
|
||||
const [selectedBoardCards, setSelectedBoardCards] = useRecoilState(
|
||||
selectedBoardCardIdsState,
|
||||
);
|
||||
const fieldsDefinitions = useRecoilValue(fieldsDefinitionsState);
|
||||
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@ -171,71 +145,40 @@ export function CompanyBoardCard() {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledBoardCardWrapper>
|
||||
<StyledBoardCard
|
||||
selected={selected}
|
||||
onClick={() => setSelected(!selected)}
|
||||
>
|
||||
<StyledBoardCardHeader>
|
||||
<CompanyChip
|
||||
id={company.id}
|
||||
name={company.name}
|
||||
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
|
||||
variant={EntityChipVariant.Transparent}
|
||||
/>
|
||||
<StyledCheckboxContainer className="checkbox-container">
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
onChange={() => setSelected(!selected)}
|
||||
variant={CheckboxVariant.Secondary}
|
||||
<EntityUpdateMutationHookContext.Provider
|
||||
value={useUpdateOnePipelineProgressMutation}
|
||||
>
|
||||
<StyledBoardCardWrapper>
|
||||
<StyledBoardCard
|
||||
selected={selected}
|
||||
onClick={() => setSelected(!selected)}
|
||||
>
|
||||
<StyledBoardCardHeader>
|
||||
<CompanyChip
|
||||
id={company.id}
|
||||
name={company.name}
|
||||
pictureUrl={getLogoUrlFromDomainName(company.domainName)}
|
||||
variant={EntityChipVariant.Transparent}
|
||||
/>
|
||||
</StyledCheckboxContainer>
|
||||
</StyledBoardCardHeader>
|
||||
<StyledBoardCardBody>
|
||||
<PreventSelectOnClickContainer>
|
||||
<DateEditableField
|
||||
icon={<IconCalendarEvent />}
|
||||
value={pipelineProgress.closeDate}
|
||||
onSubmit={(value) =>
|
||||
handleCardUpdate({
|
||||
...pipelineProgress,
|
||||
closeDate: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</PreventSelectOnClickContainer>
|
||||
<PreventSelectOnClickContainer>
|
||||
<NumberEditableField
|
||||
icon={<IconCurrencyDollar />}
|
||||
placeholder="Opportunity amount"
|
||||
value={pipelineProgress.amount}
|
||||
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>
|
||||
<StyledCheckboxContainer className="checkbox-container">
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
onChange={() => setSelected(!selected)}
|
||||
variant={CheckboxVariant.Secondary}
|
||||
/>
|
||||
</StyledCheckboxContainer>
|
||||
</StyledBoardCardHeader>
|
||||
<StyledBoardCardBody>
|
||||
{fieldsDefinitions.map((viewField) => {
|
||||
return (
|
||||
<PreventSelectOnClickContainer key={viewField.id}>
|
||||
<GenericEditableField viewField={viewField} />
|
||||
</PreventSelectOnClickContainer>
|
||||
);
|
||||
})}
|
||||
</StyledBoardCardBody>
|
||||
</StyledBoardCard>
|
||||
</StyledBoardCardWrapper>
|
||||
</EntityUpdateMutationHookContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,24 +1,13 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useInitializeCompanyBoardFilters } from '@/companies/hooks/useInitializeCompanyBoardFilters';
|
||||
import { companyProgressesFamilyState } from '@/companies/states/companyProgressesFamilyState';
|
||||
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 { pipelineViewFields } from '@/pipeline/constants/pipelineViewFields';
|
||||
import { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState';
|
||||
import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState';
|
||||
import { BoardColumnDefinition } from '@/ui/board/types/BoardColumnDefinition';
|
||||
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 { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import {
|
||||
GetPipelineProgressQuery,
|
||||
PipelineProgressableType,
|
||||
PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By,
|
||||
} from '~/generated/graphql';
|
||||
@ -29,83 +18,44 @@ import {
|
||||
useGetPipelinesQuery,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
|
||||
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
|
||||
import { CompanyBoardContext } from '../states/CompanyBoardContext';
|
||||
|
||||
export function HooksCompanyBoard({
|
||||
availableFilters,
|
||||
orderBy,
|
||||
}: {
|
||||
availableFilters: FilterDefinition[];
|
||||
orderBy: PipelineProgresses_Order_By[];
|
||||
}) {
|
||||
useInitializeCompanyBoardFilters({
|
||||
availableFilters,
|
||||
});
|
||||
const setFieldsDefinitionsState = useSetRecoilState(fieldsDefinitionsState);
|
||||
|
||||
const [currentPipeline] = useRecoilState(currentPipelineState);
|
||||
const [, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||
useEffect(() => {
|
||||
setFieldsDefinitionsState(pipelineViewFields);
|
||||
});
|
||||
|
||||
const [, setIsBoardLoaded] = useRecoilState(isBoardLoadedState);
|
||||
|
||||
const updateBoardColumns = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(pipeline: Pipeline) => {
|
||||
const currentPipeline = snapshot
|
||||
.getLoadable(currentPipelineState)
|
||||
.valueOrThrow();
|
||||
const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext);
|
||||
|
||||
const currentBoardColumns = snapshot
|
||||
.getLoadable(boardColumnsState)
|
||||
.valueOrThrow();
|
||||
const updateCompanyBoard = useUpdateCompanyBoard();
|
||||
|
||||
if (JSON.stringify(pipeline) !== JSON.stringify(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 (
|
||||
JSON.stringify(currentBoardColumns) !==
|
||||
JSON.stringify(newBoardColumns)
|
||||
) {
|
||||
setBoardColumns(newBoardColumns);
|
||||
}
|
||||
const { data: pipelineData, loading: loadingGetPipelines } =
|
||||
useGetPipelinesQuery({
|
||||
variables: {
|
||||
where: {
|
||||
pipelineProgressableType: {
|
||||
equals: PipelineProgressableType.Company,
|
||||
},
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
useGetPipelinesQuery({
|
||||
variables: {
|
||||
where: {
|
||||
pipelineProgressableType: { equals: PipelineProgressableType.Company },
|
||||
},
|
||||
},
|
||||
onCompleted: async (data) => {
|
||||
const pipeline = data?.findManyPipeline[0] as Pipeline;
|
||||
const pipeline = pipelineData?.findManyPipeline[0] as Pipeline | undefined;
|
||||
|
||||
updateBoardColumns(pipeline);
|
||||
},
|
||||
});
|
||||
|
||||
const pipelineStageIds = currentPipeline?.pipelineStages
|
||||
const pipelineStageIds = pipeline?.pipelineStages
|
||||
?.map((pipelineStage) => pipelineStage.id)
|
||||
.flat();
|
||||
|
||||
const filters = useRecoilScopedValue(filtersScopedState, CompanyBoardContext);
|
||||
|
||||
const whereFilters = useMemo(() => {
|
||||
return {
|
||||
AND: [
|
||||
@ -115,114 +65,52 @@ export function HooksCompanyBoard({
|
||||
};
|
||||
}, [filters, pipelineStageIds]) as any;
|
||||
|
||||
const updateBoardCardIds = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(
|
||||
pipelineProgresses: GetPipelineProgressQuery['findManyPipelineProgress'],
|
||||
) => {
|
||||
const boardColumns = snapshot
|
||||
.getLoadable(boardColumnsState)
|
||||
.valueOrThrow();
|
||||
const updateCompanyBoardCardIds = useUpdateCompanyBoardCardIds();
|
||||
|
||||
for (const boardColumn of boardColumns) {
|
||||
const boardCardIds = pipelineProgresses
|
||||
.filter(
|
||||
(pipelineProgressToFilter) =>
|
||||
pipelineProgressToFilter.pipelineStageId === boardColumn.id,
|
||||
)
|
||||
.map((pipelineProgress) => pipelineProgress.id);
|
||||
|
||||
set(boardCardIdsByColumnIdFamilyState(boardColumn.id), boardCardIds);
|
||||
}
|
||||
const { data: pipelineProgressData, loading: loadingGetPipelineProgress } =
|
||||
useGetPipelineProgressQuery({
|
||||
variables: {
|
||||
where: whereFilters,
|
||||
orderBy,
|
||||
},
|
||||
[],
|
||||
);
|
||||
onCompleted: (data) => {
|
||||
const pipelineProgresses = data?.findManyPipelineProgress || [];
|
||||
|
||||
const pipelineProgressesQuery = useGetPipelineProgressQuery({
|
||||
variables: {
|
||||
where: whereFilters,
|
||||
orderBy,
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
const pipelineProgresses = data?.findManyPipelineProgress || [];
|
||||
updateCompanyBoardCardIds(pipelineProgresses);
|
||||
|
||||
updateBoardCardIds(pipelineProgresses);
|
||||
setIsBoardLoaded(true);
|
||||
},
|
||||
});
|
||||
|
||||
setIsBoardLoaded(true);
|
||||
},
|
||||
});
|
||||
const pipelineProgresses = useMemo(() => {
|
||||
return pipelineProgressData?.findManyPipelineProgress || [];
|
||||
}, [pipelineProgressData]);
|
||||
|
||||
const pipelineProgresses =
|
||||
pipelineProgressesQuery.data?.findManyPipelineProgress || [];
|
||||
|
||||
const entitiesQueryResult = useGetCompaniesQuery({
|
||||
variables: {
|
||||
where: {
|
||||
id: {
|
||||
in: pipelineProgresses.map((item) => item.companyId || ''),
|
||||
const { data: companiesData, loading: loadingGetCompanies } =
|
||||
useGetCompaniesQuery({
|
||||
variables: {
|
||||
where: {
|
||||
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 =
|
||||
entitiesQueryResult.loading || pipelineProgressesQuery.loading;
|
||||
loadingGetPipelines || loadingGetPipelineProgress || loadingGetCompanies;
|
||||
|
||||
useEffect(() => {
|
||||
!loading && synchronizeCompanyProgresses(companyBoardIndex);
|
||||
}, [loading, companyBoardIndex, synchronizeCompanyProgresses]);
|
||||
if (!loading && pipeline && pipelineProgresses && companiesData) {
|
||||
updateCompanyBoard(pipeline, pipelineProgresses, companiesData.companies);
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
pipeline,
|
||||
pipelineProgresses,
|
||||
companiesData,
|
||||
updateCompanyBoard,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldNumberMetadata,
|
||||
ViewFieldRelationMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
ViewFieldURLMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import {
|
||||
IconBrandLinkedin,
|
||||
IconBuildingSkyscraper,
|
||||
@ -8,16 +18,6 @@ import {
|
||||
IconUsers,
|
||||
} from '@/ui/icon/index';
|
||||
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>[] = [
|
||||
{
|
||||
|
||||
30
front/src/modules/companies/hooks/useUpdateBoardCardIds.ts
Normal file
30
front/src/modules/companies/hooks/useUpdateBoardCardIds.ts
Normal 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);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
@ -6,6 +6,6 @@ export const companyBoardIndexState = atomFamily<
|
||||
CompanyProgress | undefined,
|
||||
string
|
||||
>({
|
||||
key: 'currentPipelineState',
|
||||
key: 'companyBoardIndexState',
|
||||
default: undefined,
|
||||
});
|
||||
|
||||
@ -7,8 +7,8 @@ import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoi
|
||||
import { useSearchPeopleQuery } from '~/generated/graphql';
|
||||
|
||||
export type OwnProps = {
|
||||
personId: string;
|
||||
onSubmit: (newPersonId: string | null) => void;
|
||||
personId: string | null;
|
||||
onSubmit: (newPersonId: PersonForSelect | null) => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ export function PeoplePicker({ personId, onSubmit, onCancel }: OwnProps) {
|
||||
|
||||
const people = useFilteredSearchEntityQuery({
|
||||
queryHook: useSearchPeopleQuery,
|
||||
selectedIds: [personId],
|
||||
selectedIds: [personId ?? ''],
|
||||
searchFilter: searchFilter,
|
||||
mappingFunction: (person) => ({
|
||||
entityType: Entity.Person,
|
||||
@ -39,7 +39,7 @@ export function PeoplePicker({ personId, onSubmit, onCancel }: OwnProps) {
|
||||
async function handleEntitySelected(
|
||||
selectedPerson: PersonForSelect | null | undefined,
|
||||
) {
|
||||
onSubmit(selectedPerson?.id ?? null);
|
||||
onSubmit(selectedPerson ?? null);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
import {
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextChipMetadata,
|
||||
ViewFieldMetadata,
|
||||
ViewFieldPhoneMetadata,
|
||||
ViewFieldRelationMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
ViewFieldURLMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import {
|
||||
IconBrandLinkedin,
|
||||
IconBriefcase,
|
||||
@ -9,16 +19,6 @@ import {
|
||||
IconUser,
|
||||
} from '@/ui/icon/index';
|
||||
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>[] = [
|
||||
{
|
||||
|
||||
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
67
front/src/modules/pipeline/constants/pipelineViewFields.tsx
Normal file
67
front/src/modules/pipeline/constants/pipelineViewFields.tsx
Normal 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>,
|
||||
];
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -20,24 +20,14 @@ export const UPDATE_PIPELINE_STAGE = gql`
|
||||
|
||||
export const UPDATE_PIPELINE_PROGRESS = gql`
|
||||
mutation UpdateOnePipelineProgress(
|
||||
$id: String
|
||||
$amount: Int
|
||||
$closeDate: DateTime
|
||||
$probability: Int
|
||||
$pointOfContactId: String
|
||||
$data: PipelineProgressUpdateInput!
|
||||
$where: PipelineProgressWhereUniqueInput!
|
||||
) {
|
||||
updateOnePipelineProgress(
|
||||
where: { id: $id }
|
||||
data: {
|
||||
amount: $amount
|
||||
closeDate: $closeDate
|
||||
probability: $probability
|
||||
pointOfContact: { connect: { id: $pointOfContactId } }
|
||||
}
|
||||
) {
|
||||
updateOnePipelineProgress(where: $where, data: $data) {
|
||||
id
|
||||
amount
|
||||
closeDate
|
||||
probability
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import React from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { Tag } from '@/ui/tag/components/Tag';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||
|
||||
@ -77,7 +75,6 @@ const StyledNumChildren = styled.div`
|
||||
type OwnProps = {
|
||||
color?: string;
|
||||
title: string;
|
||||
pipelineStageId?: string;
|
||||
onTitleEdit: (title: string) => void;
|
||||
onColumnColorEdit: (color: string) => void;
|
||||
totalAmount?: number;
|
||||
@ -104,13 +101,6 @@ export function BoardColumn({
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape, Key.Enter],
|
||||
handleClose,
|
||||
BoardColumnHotkeyScope.BoardColumn,
|
||||
[],
|
||||
);
|
||||
|
||||
function handleTitleClick() {
|
||||
setIsBoardColumnMenuOpen(true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(BoardColumnHotkeyScope.BoardColumn, {
|
||||
@ -132,7 +122,7 @@ export function BoardColumn({
|
||||
</StyledHeader>
|
||||
{isBoardColumnMenuOpen && (
|
||||
<BoardColumnMenu
|
||||
onClose={() => setIsBoardColumnMenuOpen(false)}
|
||||
onClose={handleClose}
|
||||
onTitleEdit={onTitleEdit}
|
||||
onColumnColorEdit={onColumnColorEdit}
|
||||
title={title}
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconPencil } from '@tabler/icons-react';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||
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 { BoardColumnHotkeyScope } from '../types/BoardColumnHotkeyScope';
|
||||
|
||||
import { BoardColumnEditTitleMenu } from './BoardColumnEditTitleMenu';
|
||||
|
||||
const StyledMenuContainer = styled.div`
|
||||
@ -39,6 +43,13 @@ export function BoardColumnMenu({
|
||||
callback: onClose,
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape, Key.Enter],
|
||||
onClose,
|
||||
BoardColumnHotkeyScope.BoardColumn,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledMenuContainer ref={boardColumnMenuRef}>
|
||||
<DropdownMenu>
|
||||
|
||||
@ -48,6 +48,7 @@ export function EntityBoard({
|
||||
onEditColumnColor: (columnId: string, color: string) => void;
|
||||
}) {
|
||||
const [boardColumns] = useRecoilState(boardColumnsState);
|
||||
|
||||
const theme = useTheme();
|
||||
const [updatePipelineProgressStage] =
|
||||
useUpdateOnePipelineProgressStageMutation();
|
||||
|
||||
@ -4,19 +4,15 @@ import { BoardOptions } from '../types/BoardOptions';
|
||||
|
||||
export function EntityBoardCard({
|
||||
boardOptions,
|
||||
pipelineProgressId,
|
||||
cardId,
|
||||
index,
|
||||
}: {
|
||||
boardOptions: BoardOptions;
|
||||
pipelineProgressId: string;
|
||||
cardId: string;
|
||||
index: number;
|
||||
}) {
|
||||
return (
|
||||
<Draggable
|
||||
key={pipelineProgressId}
|
||||
draggableId={pipelineProgressId}
|
||||
index={index}
|
||||
>
|
||||
<Draggable key={cardId} draggableId={cardId} index={index}>
|
||||
{(draggableProvided) => (
|
||||
<div
|
||||
ref={draggableProvided?.innerRef}
|
||||
|
||||
@ -84,7 +84,6 @@ export function EntityBoardColumn({
|
||||
onTitleEdit={handleEditColumnTitle}
|
||||
title={column.title}
|
||||
color={column.colorCode}
|
||||
pipelineStageId={column.id}
|
||||
totalAmount={boardColumnTotal}
|
||||
isFirstColumn={column.index === 0}
|
||||
numChildren={cardIds.length}
|
||||
@ -94,7 +93,7 @@ export function EntityBoardColumn({
|
||||
<BoardCardIdContext.Provider value={cardId} key={cardId}>
|
||||
<EntityBoardCard
|
||||
index={index}
|
||||
pipelineProgressId={cardId}
|
||||
cardId={cardId}
|
||||
boardOptions={boardOptions}
|
||||
/>
|
||||
</BoardCardIdContext.Provider>
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../../editable-field/types/ViewField';
|
||||
|
||||
export const FieldDefinitionContext =
|
||||
createContext<ViewFieldDefinition<ViewFieldMetadata> | null>(null);
|
||||
13
front/src/modules/ui/board/states/fieldsDefinitionsState.ts
Normal file
13
front/src/modules/ui/board/states/fieldsDefinitionsState.ts
Normal 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: [],
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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} />
|
||||
);
|
||||
}
|
||||
@ -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 <></>;
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -1,25 +1,27 @@
|
||||
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||
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 { ProbabilityFieldEditMode } from './ProbabilityFieldEditMode';
|
||||
import { ProbabilityEditableFieldEditMode } from './ProbabilityEditableFieldEditMode';
|
||||
|
||||
type OwnProps = {
|
||||
icon?: React.ReactNode;
|
||||
value: number | null | undefined;
|
||||
onSubmit?: (newValue: number) => void;
|
||||
viewField: ViewFieldDefinition<ViewFieldProbabilityMetadata>;
|
||||
};
|
||||
|
||||
export function ProbabilityEditableField({ icon, value, onSubmit }: OwnProps) {
|
||||
export function ProbabilityEditableField({ viewField }: OwnProps) {
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldContext}>
|
||||
<EditableField
|
||||
iconLabel={icon}
|
||||
iconLabel={viewField.columnIcon}
|
||||
displayModeContent={
|
||||
<ProbabilityEditableFieldEditMode viewField={viewField} />
|
||||
}
|
||||
displayModeContentOnly
|
||||
disableHoverEffect
|
||||
displayModeContent={
|
||||
<ProbabilityFieldEditMode value={value ?? 0} onChange={onSubmit} />
|
||||
}
|
||||
/>
|
||||
</RecoilScope>
|
||||
);
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { EntityIdContext } from '../states/EntityIdContext';
|
||||
|
||||
export function useCurrentEntityId() {
|
||||
return useContext(EntityIdContext);
|
||||
}
|
||||
@ -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 },
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const EditableFieldContext = createContext<string | null>(null);
|
||||
@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const EntityIdContext = createContext<string | null>(null);
|
||||
@ -0,0 +1,9 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const genericEntitiesFamilyState = atomFamily<
|
||||
Record<string, unknown> | null,
|
||||
string
|
||||
>({
|
||||
key: 'genericEntitiesFamilyState',
|
||||
default: null,
|
||||
});
|
||||
@ -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,
|
||||
})),
|
||||
});
|
||||
@ -10,7 +10,8 @@ export type ViewFieldType =
|
||||
| 'number'
|
||||
| 'date'
|
||||
| 'phone'
|
||||
| 'url';
|
||||
| 'url'
|
||||
| 'probability';
|
||||
|
||||
export type ViewFieldTextMetadata = {
|
||||
type: 'text';
|
||||
@ -72,6 +73,11 @@ export type ViewFieldDoubleTextChipMetadata = {
|
||||
entityType: Entity;
|
||||
};
|
||||
|
||||
export type ViewFieldProbabilityMetadata = {
|
||||
type: 'probability';
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type ViewFieldMetadata = { type: ViewFieldType } & (
|
||||
| ViewFieldTextMetadata
|
||||
| ViewFieldRelationMetadata
|
||||
@ -82,6 +88,7 @@ export type ViewFieldMetadata = { type: ViewFieldType } & (
|
||||
| ViewFieldURLMetadata
|
||||
| ViewFieldNumberMetadata
|
||||
| ViewFieldDateMetadata
|
||||
| ViewFieldProbabilityMetadata
|
||||
);
|
||||
|
||||
export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
|
||||
@ -101,7 +108,8 @@ export type ViewFieldChipValue = string;
|
||||
export type ViewFieldDateValue = string;
|
||||
export type ViewFieldPhoneValue = string;
|
||||
export type ViewFieldURLValue = string;
|
||||
export type ViewFieldNumberValue = number;
|
||||
export type ViewFieldNumberValue = number | null;
|
||||
export type ViewFieldProbabilityValue = number;
|
||||
|
||||
export type ViewFieldDoubleTextValue = {
|
||||
firstValue: string;
|
||||
@ -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';
|
||||
}
|
||||
@ -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'
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,3 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { EditableField } from '@/ui/editable-field/components/EditableField';
|
||||
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
|
||||
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
|
||||
@ -16,49 +14,29 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
export function DateEditableField({ icon, value, label, onSubmit }: OwnProps) {
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(value);
|
||||
}, [value]);
|
||||
|
||||
async function handleChange(newValue: string) {
|
||||
setInternalValue(newValue);
|
||||
|
||||
onSubmit?.(newValue);
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!internalValue) return;
|
||||
|
||||
onSubmit?.(internalValue);
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
setInternalValue(value);
|
||||
}
|
||||
|
||||
const internalDateValue = internalValue
|
||||
? parseDate(internalValue).toJSDate()
|
||||
: null;
|
||||
const internalDateValue = value ? parseDate(value).toJSDate() : null;
|
||||
|
||||
return (
|
||||
<RecoilScope SpecificContext={FieldContext}>
|
||||
<EditableField
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={handleCancel}
|
||||
// onSubmit={handleSubmit}
|
||||
// onCancel={handleCancel}
|
||||
iconLabel={icon}
|
||||
label={label}
|
||||
editModeContent={
|
||||
<EditableFieldEditModeDate
|
||||
value={internalValue || new Date().toISOString()}
|
||||
value={value || new Date().toISOString()}
|
||||
onChange={(newValue: string) => {
|
||||
handleChange(newValue);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
displayModeContent={<DateInputDisplay value={internalDateValue} />}
|
||||
isDisplayModeContentEmpty={!internalValue}
|
||||
isDisplayModeContentEmpty={!value}
|
||||
/>
|
||||
</RecoilScope>
|
||||
);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { parseDate } from '~/utils/date-utils';
|
||||
@ -11,6 +13,12 @@ type OwnProps = {
|
||||
};
|
||||
|
||||
export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(value);
|
||||
}, [value]);
|
||||
|
||||
const { closeEditableField } = useEditableField();
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
@ -20,7 +28,7 @@ export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
|
||||
|
||||
return (
|
||||
<DateInputEdit
|
||||
value={parseDate(value).toJSDate()}
|
||||
value={internalValue ? parseDate(internalValue).toJSDate() : new Date()}
|
||||
onChange={(newDate: Date) => {
|
||||
handleChange(newDate.toISOString());
|
||||
}}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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 = {};
|
||||
@ -0,0 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { FilterDefinition } from '../types/FilterDefinition';
|
||||
|
||||
export const AvailableFiltersContext = createContext<FilterDefinition[]>([]);
|
||||
@ -12,7 +12,7 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../types/ViewField';
|
||||
} from '../../editable-field/types/ViewField';
|
||||
|
||||
const StyledColumnMenu = styled(DropdownMenu)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
|
||||
@ -13,6 +13,10 @@ import {
|
||||
useUpdateViewFieldMutation,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../../editable-field/types/ViewField';
|
||||
import { toViewFieldInput } from '../hooks/useLoadView';
|
||||
import { resizeFieldOffsetState } from '../states/resizeFieldOffsetState';
|
||||
import {
|
||||
@ -21,10 +25,6 @@ import {
|
||||
viewFieldsState,
|
||||
visibleViewFieldsState,
|
||||
} from '../states/viewFieldsState';
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../types/ViewField';
|
||||
|
||||
import { ColumnHead } from './ColumnHead';
|
||||
import { EntityTableColumnMenu } from './EntityTableColumnMenu';
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { defaultOrderBy } from '@/people/queries';
|
||||
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
|
||||
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -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 {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
|
||||
import { isViewFieldChip } from '../../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 { isViewFieldChip } from '../../../editable-field/types/guards/isViewFieldChip';
|
||||
import { GenericEditableChipCell } from '../type/components/GenericEditableChipCell';
|
||||
import { GenericEditableDateCell } from '../type/components/GenericEditableDateCell';
|
||||
import { GenericEditableDoubleTextCell } from '../type/components/GenericEditableDoubleTextCell';
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
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 { GenericEditableChipCellEditMode } from './GenericEditableChipCellEditMode';
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
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 {
|
||||
ViewFieldChipMetadata,
|
||||
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';
|
||||
|
||||
type OwnProps = {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldChipMetadata,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDateMetadata,
|
||||
ViewFieldDefinition,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableDateCellEditMode } from './GenericEditableDateCellEditMode';
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { DateTime } from 'luxon';
|
||||
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 {
|
||||
ViewFieldDateMetadata,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldDoubleTextMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableDoubleTextCellEditMode } from './GenericEditableDoubleTextCellEditMode';
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
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 { GenericEditableDoubleTextChipCellEditMode } from './GenericEditableDoubleTextChipCellEditMode';
|
||||
|
||||
@ -2,13 +2,13 @@ import { useRecoilState } from 'recoil';
|
||||
|
||||
import { CompanyChip } from '@/companies/components/CompanyChip';
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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 = {
|
||||
viewField: ViewFieldDefinition<ViewFieldDoubleTextChipMetadata>;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldPhoneMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldPhoneMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditablePhoneCellEditMode } from './GenericEditablePhoneCellEditMode';
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
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 { GenericEditableRelationCellEditMode } from './GenericEditableRelationCellEditMode';
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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 { getLogoUrlFromDomainName } from '~/utils';
|
||||
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
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 { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
|
||||
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { useUpdateEntityField } from '@/ui/table/hooks/useUpdateEntityField';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldRelationMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
import { UserPicker } from '@/users/components/UserPicker';
|
||||
|
||||
type OwnProps = {
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
|
||||
import { GenericEditableTextCellEditMode } from './GenericEditableTextCellEditMode';
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldURLMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { InplaceInputURLDisplayMode } from '@/ui/input/url/components/URLTextInputDisplay';
|
||||
import { EditableCell } from '@/ui/table/editable-cell/components/EditableCell';
|
||||
import { useCurrentRowEntityId } from '@/ui/table/hooks/useCurrentEntityId';
|
||||
import { tableEntityFieldFamilySelector } from '@/ui/table/states/tableEntityFieldFamilySelector';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldURLMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
import { sanitizeURL } from '~/utils';
|
||||
|
||||
import { GenericEditableURLCellEditMode } from './GenericEditableURLCellEditMode';
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
ViewFieldDefinition,
|
||||
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';
|
||||
|
||||
|
||||
@ -8,13 +8,13 @@ import {
|
||||
useGetViewFieldsQuery,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { entityTableDimensionsState } from '../states/entityTableDimensionsState';
|
||||
import { viewFieldsState } from '../states/viewFieldsState';
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
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 = {
|
||||
type: 'text',
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
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 { 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 { 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 { isViewFieldChipValue } from '../../editable-field/types/guards/isViewFieldChipValue';
|
||||
import {
|
||||
ViewFieldChipMetadata,
|
||||
ViewFieldChipValue,
|
||||
@ -41,7 +41,7 @@ import {
|
||||
ViewFieldTextValue,
|
||||
ViewFieldURLMetadata,
|
||||
ViewFieldURLValue,
|
||||
} from '../types/ViewField';
|
||||
} from '../../editable-field/types/ViewField';
|
||||
|
||||
export function useUpdateEntityField() {
|
||||
const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { ViewFieldDefinition, ViewFieldMetadata } from '../types/ViewField';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../../editable-field/types/ViewField';
|
||||
|
||||
export const ViewFieldContext =
|
||||
createContext<ViewFieldDefinition<ViewFieldMetadata> | null>(null);
|
||||
|
||||
@ -6,7 +6,7 @@ import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../types/ViewField';
|
||||
} from '../../editable-field/types/ViewField';
|
||||
|
||||
export const viewFieldsState = atom<{
|
||||
objectName: 'company' | 'person' | '';
|
||||
|
||||
@ -8,6 +8,10 @@ import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader'
|
||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
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 { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
||||
import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon';
|
||||
@ -15,10 +19,6 @@ import {
|
||||
hiddenViewFieldsState,
|
||||
visibleViewFieldsState,
|
||||
} from '@/ui/table/states/viewFieldsState';
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
import { useUpdateViewFieldMutation } from '~/generated/graphql';
|
||||
|
||||
import { GET_VIEW_FIELDS } from '../queries/select';
|
||||
|
||||
@ -10,7 +10,7 @@ import { DropdownMenuSubheader } from '@/ui/dropdown/components/DropdownMenuSubh
|
||||
import {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/table/types/ViewField';
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
|
||||
type OptionsDropdownSectionProps = {
|
||||
renderActions: (
|
||||
|
||||
@ -14,6 +14,7 @@ import { EntityBoard } from '@/ui/board/components/EntityBoard';
|
||||
import { EntityBoardActionBar } from '@/ui/board/components/EntityBoardActionBar';
|
||||
import { BoardOptionsContext } from '@/ui/board/states/BoardOptionsContext';
|
||||
import { reduceSortsToOrderBy } from '@/ui/filter-n-sort/helpers';
|
||||
import { AvailableFiltersContext } from '@/ui/filter-n-sort/states/AvailableFiltersContext';
|
||||
import { IconTargetArrow } from '@/ui/icon/index';
|
||||
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
@ -83,19 +84,20 @@ export function Opportunities() {
|
||||
>
|
||||
<BoardOptionsContext.Provider value={opportunitiesBoardOptions}>
|
||||
<RecoilScope SpecificContext={CompanyBoardContext}>
|
||||
<HooksCompanyBoard
|
||||
availableFilters={opportunitiesBoardOptions.filters}
|
||||
orderBy={orderBy}
|
||||
/>
|
||||
<EntityBoard
|
||||
boardOptions={opportunitiesBoardOptions}
|
||||
updateSorts={updateSorts}
|
||||
onEditColumnColor={handleEditColumnColor}
|
||||
onEditColumnTitle={handleEditColumnTitle}
|
||||
/>
|
||||
<EntityBoardActionBar>
|
||||
<BoardActionBarButtonDeleteBoardCard onDelete={handleDelete} />
|
||||
</EntityBoardActionBar>
|
||||
<AvailableFiltersContext.Provider
|
||||
value={opportunitiesBoardOptions.filters}
|
||||
>
|
||||
<HooksCompanyBoard orderBy={orderBy} />
|
||||
<EntityBoard
|
||||
boardOptions={opportunitiesBoardOptions}
|
||||
updateSorts={updateSorts}
|
||||
onEditColumnColor={handleEditColumnColor}
|
||||
onEditColumnTitle={handleEditColumnTitle}
|
||||
/>
|
||||
<EntityBoardActionBar>
|
||||
<BoardActionBarButtonDeleteBoardCard onDelete={handleDelete} />
|
||||
</EntityBoardActionBar>
|
||||
</AvailableFiltersContext.Provider>
|
||||
</RecoilScope>
|
||||
</BoardOptionsContext.Provider>
|
||||
</WithTopBarContainer>
|
||||
|
||||
@ -129,11 +129,13 @@ export function AuthAutoRouter() {
|
||||
}
|
||||
}
|
||||
|
||||
eventTracker('pageview', {
|
||||
location: {
|
||||
pathname: location.pathname,
|
||||
},
|
||||
});
|
||||
setTimeout(() => {
|
||||
eventTracker('pageview', {
|
||||
location: {
|
||||
pathname: location.pathname,
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
}, [
|
||||
onboardingStatus,
|
||||
navigate,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user