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