diff --git a/front/src/AppNavbar.tsx b/front/src/AppNavbar.tsx index 2d273f6a1..34436bc2f 100644 --- a/front/src/AppNavbar.tsx +++ b/front/src/AppNavbar.tsx @@ -2,6 +2,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { useTheme } from '@emotion/react'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; +import { Favorites } from '@/favorites/components/Favorites'; import { SettingsNavbar } from '@/settings/components/SettingsNavbar'; import { IconBell, @@ -56,6 +57,7 @@ export function AppNavbar() { active={currentPath === '/tasks'} icon={} /> + >; + Favorite?: Maybe>; PipelineProgress?: Maybe>; _activityCount: Scalars['Int']; accountOwner?: Maybe; @@ -659,6 +660,7 @@ export type Company = { export type CompanyCreateInput = { ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; PipelineProgress?: InputMaybe; accountOwner?: InputMaybe; address: Scalars['String']; @@ -696,6 +698,7 @@ export type CompanyOrderByRelationAggregateInput = { export type CompanyOrderByWithRelationInput = { ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; PipelineProgress?: InputMaybe; accountOwner?: InputMaybe; accountOwnerId?: InputMaybe; @@ -731,6 +734,7 @@ export enum CompanyScalarFieldEnum { export type CompanyUpdateInput = { ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; PipelineProgress?: InputMaybe; accountOwner?: InputMaybe; address?: InputMaybe; @@ -769,6 +773,7 @@ export type CompanyUpdateOneWithoutPipelineProgressNestedInput = { export type CompanyWhereInput = { AND?: InputMaybe>; ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; NOT?: InputMaybe>; OR?: InputMaybe>; PipelineProgress?: InputMaybe; @@ -860,6 +865,71 @@ export type EnumViewTypeFilter = { notIn?: InputMaybe>; }; +export type Favorite = { + __typename?: 'Favorite'; + company?: Maybe; + companyId?: Maybe; + id: Scalars['ID']; + person?: Maybe; + personId?: Maybe; + workspaceId?: Maybe; + workspaceMember?: Maybe; + workspaceMemberId?: Maybe; +}; + +export type FavoriteCreateNestedManyWithoutCompanyInput = { + connect?: InputMaybe>; +}; + +export type FavoriteCreateNestedManyWithoutPersonInput = { + connect?: InputMaybe>; +}; + +export type FavoriteListRelationFilter = { + every?: InputMaybe; + none?: InputMaybe; + some?: InputMaybe; +}; + +export type FavoriteMutationForCompanyArgs = { + companyId: Scalars['String']; +}; + +export type FavoriteMutationForPersonArgs = { + personId: Scalars['String']; +}; + +export type FavoriteOrderByRelationAggregateInput = { + _count?: InputMaybe; +}; + +export type FavoriteUpdateManyWithoutCompanyNestedInput = { + connect?: InputMaybe>; + disconnect?: InputMaybe>; + set?: InputMaybe>; +}; + +export type FavoriteUpdateManyWithoutPersonNestedInput = { + connect?: InputMaybe>; + disconnect?: InputMaybe>; + set?: InputMaybe>; +}; + +export type FavoriteWhereInput = { + AND?: InputMaybe>; + NOT?: InputMaybe>; + OR?: InputMaybe>; + companyId?: InputMaybe; + id?: InputMaybe; + personId?: InputMaybe; + workspaceId?: InputMaybe; + workspaceMemberId?: InputMaybe; +}; + +export type FavoriteWhereUniqueInput = { + id?: InputMaybe; +}; + export enum FileFolder { Attachment = 'Attachment', PersonPicture = 'PersonPicture', @@ -915,6 +985,8 @@ export type Mutation = { allowImpersonation: WorkspaceMember; challenge: LoginToken; createEvent: Analytics; + createFavoriteForCompany: Favorite; + createFavoriteForPerson: Favorite; createManyViewField: AffectedRows; createManyViewSort: AffectedRows; createOneActivity: Activity; @@ -924,6 +996,7 @@ export type Mutation = { createOnePipelineProgress: PipelineProgress; createOneViewField: ViewField; deleteCurrentWorkspace: Workspace; + deleteFavorite: Favorite; deleteManyActivities: AffectedRows; deleteManyCompany: AffectedRows; deleteManyPerson: AffectedRows; @@ -970,6 +1043,16 @@ export type MutationCreateEventArgs = { }; +export type MutationCreateFavoriteForCompanyArgs = { + data: FavoriteMutationForCompanyArgs; +}; + + +export type MutationCreateFavoriteForPersonArgs = { + data: FavoriteMutationForPersonArgs; +}; + + export type MutationCreateManyViewFieldArgs = { data: Array; skipDuplicates?: InputMaybe; @@ -1012,6 +1095,11 @@ export type MutationCreateOneViewFieldArgs = { }; +export type MutationDeleteFavoriteArgs = { + where: FavoriteWhereInput; +}; + + export type MutationDeleteManyActivitiesArgs = { where?: InputMaybe; }; @@ -1279,6 +1367,7 @@ export type NestedStringNullableFilter = { export type Person = { __typename?: 'Person'; ActivityTarget?: Maybe>; + Favorite?: Maybe>; PipelineProgress?: Maybe>; _activityCount: Scalars['Int']; activities: Array; @@ -1303,6 +1392,7 @@ export type Person = { export type PersonCreateInput = { ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; PipelineProgress?: InputMaybe; avatarUrl?: InputMaybe; city?: InputMaybe; @@ -1348,6 +1438,7 @@ export type PersonOrderByRelationAggregateInput = { export type PersonOrderByWithRelationInput = { ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; PipelineProgress?: InputMaybe; avatarUrl?: InputMaybe; city?: InputMaybe; @@ -1391,6 +1482,7 @@ export enum PersonScalarFieldEnum { export type PersonUpdateInput = { ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; PipelineProgress?: InputMaybe; avatarUrl?: InputMaybe; city?: InputMaybe; @@ -1433,6 +1525,7 @@ export type PersonUpdateOneWithoutPipelineProgressNestedInput = { export type PersonWhereInput = { AND?: InputMaybe>; ActivityTarget?: InputMaybe; + Favorite?: InputMaybe; NOT?: InputMaybe>; OR?: InputMaybe>; PipelineProgress?: InputMaybe; @@ -1806,6 +1899,7 @@ export type Query = { clientConfig: ClientConfig; currentUser: User; currentWorkspace: Workspace; + findFavorites: Array; findManyActivities: Array; findManyCompany: Array; findManyPerson: Array; @@ -2507,6 +2601,7 @@ export type WorkspaceInviteHashValid = { export type WorkspaceMember = { __typename?: 'WorkspaceMember'; + Favorite?: Maybe>; allowImpersonation: Scalars['Boolean']; createdAt: Scalars['DateTime']; id: Scalars['ID']; @@ -2517,6 +2612,7 @@ export type WorkspaceMember = { }; export type WorkspaceMemberOrderByWithRelationInput = { + Favorite?: InputMaybe; allowImpersonation?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; @@ -2543,6 +2639,7 @@ export type WorkspaceMemberUpdateManyWithoutWorkspaceNestedInput = { export type WorkspaceMemberWhereInput = { AND?: InputMaybe>; + Favorite?: InputMaybe; NOT?: InputMaybe>; OR?: InputMaybe>; allowImpersonation?: InputMaybe; @@ -2734,7 +2831,7 @@ export type GetCompanyQueryVariables = Exact<{ }>; -export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } }; +export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null, Favorite?: Array<{ __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string } | null, company?: { __typename?: 'Company', id: string } | null }> | null } }; export type UpdateOneCompanyMutationVariables = Exact<{ where: CompanyWhereUniqueInput; @@ -2760,6 +2857,32 @@ export type DeleteManyCompaniesMutationVariables = Exact<{ export type DeleteManyCompaniesMutation = { __typename?: 'Mutation', deleteManyCompany: { __typename?: 'AffectedRows', count: number } }; +export type GetFavoritesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetFavoritesQuery = { __typename?: 'Query', findFavorites: Array<{ __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } | null, company?: { __typename?: 'Company', id: string, name: string, domainName: string, accountOwner?: { __typename?: 'User', id: string, displayName: string, avatarUrl?: string | null } | null } | null }> }; + +export type InsertPersonFavoriteMutationVariables = Exact<{ + data: FavoriteMutationForPersonArgs; +}>; + + +export type InsertPersonFavoriteMutation = { __typename?: 'Mutation', createFavoriteForPerson: { __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string } | null } }; + +export type InsertCompanyFavoriteMutationVariables = Exact<{ + data: FavoriteMutationForCompanyArgs; +}>; + + +export type InsertCompanyFavoriteMutation = { __typename?: 'Mutation', createFavoriteForCompany: { __typename?: 'Favorite', id: string, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null } }; + +export type DeleteFavoriteMutationVariables = Exact<{ + where: FavoriteWhereInput; +}>; + + +export type DeleteFavoriteMutation = { __typename?: 'Mutation', deleteFavorite: { __typename?: 'Favorite', id: string } }; + export type GetPeopleQueryVariables = Exact<{ orderBy?: InputMaybe | PersonOrderByWithRelationInput>; where?: InputMaybe; @@ -2823,7 +2946,7 @@ export type GetPersonQueryVariables = Exact<{ }>; -export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, email?: string | null, createdAt: string, city?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, xUrl?: string | null, avatarUrl?: string | null, phone?: string | null, _activityCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null } }; +export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string, email?: string | null, createdAt: string, city?: string | null, jobTitle?: string | null, linkedinUrl?: string | null, xUrl?: string | null, avatarUrl?: string | null, phone?: string | null, _activityCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null, Favorite?: Array<{ __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string } | null, company?: { __typename?: 'Company', id: string } | null }> | null } }; export type UpdateOnePersonMutationVariables = Exact<{ where: PersonWhereUniqueInput; @@ -4097,6 +4220,15 @@ export const GetCompanyDocument = gql` displayName avatarUrl } + Favorite { + id + person { + id + } + company { + id + } + } } } `; @@ -4241,6 +4373,166 @@ export function useDeleteManyCompaniesMutation(baseOptions?: Apollo.MutationHook export type DeleteManyCompaniesMutationHookResult = ReturnType; export type DeleteManyCompaniesMutationResult = Apollo.MutationResult; export type DeleteManyCompaniesMutationOptions = Apollo.BaseMutationOptions; +export const GetFavoritesDocument = gql` + query GetFavorites { + findFavorites { + id + person { + id + firstName + lastName + avatarUrl + } + company { + id + name + domainName + accountOwner { + id + displayName + avatarUrl + } + } + } +} + `; + +/** + * __useGetFavoritesQuery__ + * + * To run a query within a React component, call `useGetFavoritesQuery` and pass it any options that fit your needs. + * When your component renders, `useGetFavoritesQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetFavoritesQuery({ + * variables: { + * }, + * }); + */ +export function useGetFavoritesQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetFavoritesDocument, options); + } +export function useGetFavoritesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetFavoritesDocument, options); + } +export type GetFavoritesQueryHookResult = ReturnType; +export type GetFavoritesLazyQueryHookResult = ReturnType; +export type GetFavoritesQueryResult = Apollo.QueryResult; +export const InsertPersonFavoriteDocument = gql` + mutation InsertPersonFavorite($data: FavoriteMutationForPersonArgs!) { + createFavoriteForPerson(data: $data) { + id + person { + id + firstName + lastName + displayName + } + } +} + `; +export type InsertPersonFavoriteMutationFn = Apollo.MutationFunction; + +/** + * __useInsertPersonFavoriteMutation__ + * + * To run a mutation, you first call `useInsertPersonFavoriteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useInsertPersonFavoriteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [insertPersonFavoriteMutation, { data, loading, error }] = useInsertPersonFavoriteMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useInsertPersonFavoriteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(InsertPersonFavoriteDocument, options); + } +export type InsertPersonFavoriteMutationHookResult = ReturnType; +export type InsertPersonFavoriteMutationResult = Apollo.MutationResult; +export type InsertPersonFavoriteMutationOptions = Apollo.BaseMutationOptions; +export const InsertCompanyFavoriteDocument = gql` + mutation InsertCompanyFavorite($data: FavoriteMutationForCompanyArgs!) { + createFavoriteForCompany(data: $data) { + id + company { + id + name + domainName + } + } +} + `; +export type InsertCompanyFavoriteMutationFn = Apollo.MutationFunction; + +/** + * __useInsertCompanyFavoriteMutation__ + * + * To run a mutation, you first call `useInsertCompanyFavoriteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useInsertCompanyFavoriteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [insertCompanyFavoriteMutation, { data, loading, error }] = useInsertCompanyFavoriteMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useInsertCompanyFavoriteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(InsertCompanyFavoriteDocument, options); + } +export type InsertCompanyFavoriteMutationHookResult = ReturnType; +export type InsertCompanyFavoriteMutationResult = Apollo.MutationResult; +export type InsertCompanyFavoriteMutationOptions = Apollo.BaseMutationOptions; +export const DeleteFavoriteDocument = gql` + mutation DeleteFavorite($where: FavoriteWhereInput!) { + deleteFavorite(where: $where) { + id + } +} + `; +export type DeleteFavoriteMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteFavoriteMutation__ + * + * To run a mutation, you first call `useDeleteFavoriteMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteFavoriteMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deleteFavoriteMutation, { data, loading, error }] = useDeleteFavoriteMutation({ + * variables: { + * where: // value for 'where' + * }, + * }); + */ +export function useDeleteFavoriteMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeleteFavoriteDocument, options); + } +export type DeleteFavoriteMutationHookResult = ReturnType; +export type DeleteFavoriteMutationResult = Apollo.MutationResult; +export type DeleteFavoriteMutationOptions = Apollo.BaseMutationOptions; export const GetPeopleDocument = gql` query GetPeople($orderBy: [PersonOrderByWithRelationInput!], $where: PersonWhereInput, $limit: Int) { people: findManyPerson(orderBy: $orderBy, where: $where, take: $limit) { @@ -4575,6 +4867,15 @@ export const GetPersonDocument = gql` name domainName } + Favorite { + id + person { + id + } + company { + id + } + } } } `; diff --git a/front/src/modules/activities/components/TaskList.tsx b/front/src/modules/activities/components/TaskList.tsx index f2503273e..8d97f0d0f 100644 --- a/front/src/modules/activities/components/TaskList.tsx +++ b/front/src/modules/activities/components/TaskList.tsx @@ -20,6 +20,7 @@ const StyledContainer = styled.div` const StyledTitle = styled.h3` color: ${({ theme }) => theme.font.color.primary}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; margin-bottom: ${({ theme }) => theme.spacing(4)}; margin-top: ${({ theme }) => theme.spacing(4)}; `; diff --git a/front/src/modules/activities/hooks/useCompleteTask.ts b/front/src/modules/activities/hooks/useCompleteTask.ts index 9b535c259..ecf6973e2 100644 --- a/front/src/modules/activities/hooks/useCompleteTask.ts +++ b/front/src/modules/activities/hooks/useCompleteTask.ts @@ -16,8 +16,6 @@ export function useCompleteTask(task: Task) { fragment: ACTIVITY_UPDATE_FRAGMENT, }); - console.log('cachedTask', cachedTask); - const completeTask = useCallback( (value: boolean) => { const completedAt = value ? new Date().toISOString() : null; diff --git a/front/src/modules/auth/components/Logo.tsx b/front/src/modules/auth/components/Logo.tsx index 0d60d69bc..d12b997a0 100644 --- a/front/src/modules/auth/components/Logo.tsx +++ b/front/src/modules/auth/components/Logo.tsx @@ -6,40 +6,62 @@ type Props = React.ComponentProps<'div'> & { workspaceLogo?: string | null; }; -const StyledLogo = styled.div` +const StyledContainer = styled.div` height: 48px; margin-bottom: ${({ theme }) => theme.spacing(4)}; margin-top: ${({ theme }) => theme.spacing(4)}; - img { - height: 100%; - width: 100%; - } - position: relative; width: 48px; `; -type StyledWorkspaceLogoProps = { +const StyledTwentyLogo = styled.img` + border-radius: ${({ theme }) => theme.border.radius.xs}; + height: 24px; + width: 24px; +`; + +const StyledTwentyLogoContainer = styled.div` + align-items: center; + background-color: ${({ theme }) => theme.background.primary}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + bottom: ${({ theme }) => `-${theme.spacing(3)}`}; + display: flex; + height: 28px; + justify-content: center; + + position: absolute; + right: ${({ theme }) => `-${theme.spacing(3)}`}; + width: 28px; +`; + +type StyledMainLogoProps = { logo?: string | null; }; -const StyledWorkspaceLogo = styled.div` +const StyledMainLogo = styled.div` background: url(${(props) => props.logo}); background-size: cover; - border-radius: ${({ theme }) => theme.border.radius.xs}; - bottom: ${({ theme }) => `-${theme.spacing(3)}`}; - height: ${({ theme }) => theme.spacing(6)}; - position: absolute; - right: ${({ theme }) => `-${theme.spacing(3)}`}; - width: ${({ theme }) => theme.spacing(6)}; + height: 100%; + + width: 100%; `; export function Logo({ workspaceLogo, ...props }: Props) { + if (!workspaceLogo) { + return ( + + + + ); + } + return ( - - - logo - + + + + + + ); } diff --git a/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx b/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx index 614893a8d..489b2b188 100644 --- a/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx +++ b/front/src/modules/auth/sign-in-up/components/SignInUpForm.tsx @@ -76,7 +76,7 @@ export function SignInUpForm() { const title = useMemo(() => { if (signInUpMode === SignInUpMode.Invite) { - return `Join ${workspace?.displayName ?? ''} Team`; + return `Join ${workspace?.displayName ?? ''} team`; } return signInUpMode === SignInUpMode.SignIn diff --git a/front/src/modules/companies/components/CompanyBoardCard.tsx b/front/src/modules/companies/components/CompanyBoardCard.tsx index a03070240..1974fa1a8 100644 --- a/front/src/modules/companies/components/CompanyBoardCard.tsx +++ b/front/src/modules/companies/components/CompanyBoardCard.tsx @@ -3,17 +3,18 @@ import styled from '@emotion/styled'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; -import { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState'; import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState'; +import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState'; import { EntityChipVariant } from '@/ui/chip/components/EntityChip'; import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField'; +import { EditableFieldDefinitionContext } from '@/ui/editable-field/states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '@/ui/editable-field/states/EditableFieldEntityIdContext'; +import { EditableFieldMutationContext } from '@/ui/editable-field/states/EditableFieldMutationContext'; import { Checkbox, CheckboxVariant, } from '@/ui/input/checkbox/components/Checkbox'; import { actionBarOpenState } from '@/ui/table/states/ActionBarIsOpenState'; -import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext'; import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql'; import { getLogoUrlFromDomainName } from '~/utils'; @@ -112,7 +113,7 @@ export function CompanyBoardCard() { const [selectedBoardCards, setSelectedBoardCards] = useRecoilState( selectedBoardCardIdsState, ); - const fieldsDefinitions = useRecoilValue(fieldsDefinitionsState); + const viewFieldsDefinitions = useRecoilValue(viewFieldsDefinitionsState); const selected = selectedBoardCards.includes(boardCardId ?? ''); const setActionBarOpenState = useSetRecoilState(actionBarOpenState); @@ -128,7 +129,8 @@ export function CompanyBoardCard() { } } - if (!company || !pipelineProgress) { + // boardCardId check can be moved to a wrapper to avoid unnecessary logic above + if (!company || !pipelineProgress || !boardCardId) { return null; } @@ -149,42 +151,52 @@ export function CompanyBoardCard() { } return ( - - - setSelected(!selected)} - > - - + setSelected(!selected)} + > + + + + setSelected(!selected)} + variant={CheckboxVariant.Secondary} /> - - setSelected(!selected)} - variant={CheckboxVariant.Secondary} - /> - - - - {fieldsDefinitions.map((viewField) => { - return ( - - - - - - ); - })} - - - - + + + + + + {viewFieldsDefinitions.map((viewField) => { + return ( + + + + + + ); + })} + + + + + ); } diff --git a/front/src/modules/companies/components/CompanyTeam.tsx b/front/src/modules/companies/components/CompanyTeam.tsx index eb7e057b2..ded54eddd 100644 --- a/front/src/modules/companies/components/CompanyTeam.tsx +++ b/front/src/modules/companies/components/CompanyTeam.tsx @@ -12,6 +12,7 @@ const StyledContainer = styled.div` display: flex; flex-direction: column; gap: ${({ theme }) => theme.spacing(2)}; + margin-bottom: ${({ theme }) => theme.spacing(2)}; `; const StyledTitleContainer = styled.div` @@ -32,7 +33,6 @@ const StyledListContainer = styled.div` border-radius: ${({ theme }) => theme.spacing(1)}; display: flex; flex-direction: column; - max-height: ${({ theme }) => theme.spacing(35)}; overflow: auto; width: 100%; `; @@ -63,8 +63,12 @@ export function CompanyTeam({ company }: CompanyTeamPropsType) { Team - {data?.people?.map((person) => ( - + {data?.people?.map((person, id) => ( + ))} diff --git a/front/src/modules/companies/components/HooksCompanyBoard.tsx b/front/src/modules/companies/components/HooksCompanyBoard.tsx index 0bde12d68..ffda2cf6e 100644 --- a/front/src/modules/companies/components/HooksCompanyBoard.tsx +++ b/front/src/modules/companies/components/HooksCompanyBoard.tsx @@ -2,8 +2,8 @@ import { useEffect, useMemo } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; import { pipelineViewFields } from '@/pipeline/constants/pipelineViewFields'; -import { fieldsDefinitionsState } from '@/ui/board/states/fieldsDefinitionsState'; import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState'; +import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState'; import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; @@ -27,7 +27,9 @@ export function HooksCompanyBoard({ }: { orderBy: PipelineProgresses_Order_By[]; }) { - const setFieldsDefinitionsState = useSetRecoilState(fieldsDefinitionsState); + const setFieldsDefinitionsState = useSetRecoilState( + viewFieldsDefinitionsState, + ); useEffect(() => { setFieldsDefinitionsState(pipelineViewFields); diff --git a/front/src/modules/companies/queries/show.ts b/front/src/modules/companies/queries/show.ts index 73fc25a12..ebc4d63ab 100644 --- a/front/src/modules/companies/queries/show.ts +++ b/front/src/modules/companies/queries/show.ts @@ -19,6 +19,15 @@ export const GET_COMPANY = gql` displayName avatarUrl } + Favorite { + id + person { + id + } + company { + id + } + } } } `; diff --git a/front/src/modules/favorites/components/Favorites.tsx b/front/src/modules/favorites/components/Favorites.tsx new file mode 100644 index 000000000..a2da7c1b9 --- /dev/null +++ b/front/src/modules/favorites/components/Favorites.tsx @@ -0,0 +1,63 @@ +import styled from '@emotion/styled'; + +import NavItem from '@/ui/navbar/components/NavItem'; +import NavTitle from '@/ui/navbar/components/NavTitle'; +import { Avatar } from '@/users/components/Avatar'; +import { useGetFavoritesQuery } from '~/generated/graphql'; +import { getLogoUrlFromDomainName } from '~/utils'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + overflow-x: auto; + width: 100%; +`; + +export function Favorites() { + const { data } = useGetFavoritesQuery(); + const favorites = data?.findFavorites; + + if (!favorites || favorites.length === 0) return <>; + + return ( + + + {favorites.map( + ({ id, person, company }) => + (person && ( + + } + to={`/person/${person.id}`} + /> + )) || + (company && ( + + } + to={`/companies/${company.id}`} + /> + )), + )} + + ); +} diff --git a/front/src/modules/favorites/hooks/useFavorites.ts b/front/src/modules/favorites/hooks/useFavorites.ts new file mode 100644 index 000000000..345150caa --- /dev/null +++ b/front/src/modules/favorites/hooks/useFavorites.ts @@ -0,0 +1,84 @@ +import { getOperationName } from '@apollo/client/utilities'; + +import { GET_COMPANY } from '@/companies/queries'; +import { GET_PERSON } from '@/people/queries/show'; +import { + useDeleteFavoriteMutation, + useInsertCompanyFavoriteMutation, + useInsertPersonFavoriteMutation, +} from '~/generated/graphql'; + +import { GET_FAVORITES } from '../queries/show'; + +export function useFavorites() { + const [insertCompanyFavoriteMutation] = useInsertCompanyFavoriteMutation(); + const [insertPersonFavoriteMutation] = useInsertPersonFavoriteMutation(); + const [deleteFavoriteMutation] = useDeleteFavoriteMutation(); + + function insertCompanyFavorite(companyId: string) { + insertCompanyFavoriteMutation({ + variables: { + data: { + companyId, + }, + }, + refetchQueries: [ + getOperationName(GET_FAVORITES) ?? '', + getOperationName(GET_COMPANY) ?? '', + ], + }); + } + + function insertPersonFavorite(personId: string) { + insertPersonFavoriteMutation({ + variables: { + data: { + personId, + }, + }, + refetchQueries: [ + getOperationName(GET_FAVORITES) ?? '', + getOperationName(GET_PERSON) ?? '', + ], + }); + } + + function deleteCompanyFavorite(companyId: string) { + deleteFavoriteMutation({ + variables: { + where: { + companyId: { + equals: companyId, + }, + }, + }, + refetchQueries: [ + getOperationName(GET_FAVORITES) ?? '', + getOperationName(GET_COMPANY) ?? '', + ], + }); + } + + function deletePersonFavorite(personId: string) { + deleteFavoriteMutation({ + variables: { + where: { + personId: { + equals: personId, + }, + }, + }, + refetchQueries: [ + getOperationName(GET_FAVORITES) ?? '', + getOperationName(GET_PERSON) ?? '', + ], + }); + } + + return { + insertCompanyFavorite, + insertPersonFavorite, + deleteCompanyFavorite, + deletePersonFavorite, + }; +} diff --git a/front/src/modules/favorites/queries/show.ts b/front/src/modules/favorites/queries/show.ts new file mode 100644 index 000000000..a41de5b08 --- /dev/null +++ b/front/src/modules/favorites/queries/show.ts @@ -0,0 +1,25 @@ +import { gql } from '@apollo/client'; + +export const GET_FAVORITES = gql` + query GetFavorites { + findFavorites { + id + person { + id + firstName + lastName + avatarUrl + } + company { + id + name + domainName + accountOwner { + id + displayName + avatarUrl + } + } + } + } +`; diff --git a/front/src/modules/favorites/queries/update.ts b/front/src/modules/favorites/queries/update.ts new file mode 100644 index 000000000..b9ab1a17e --- /dev/null +++ b/front/src/modules/favorites/queries/update.ts @@ -0,0 +1,36 @@ +import { gql } from '@apollo/client'; + +export const INSERT_PERSON_FAVORITE = gql` + mutation InsertPersonFavorite($data: FavoriteMutationForPersonArgs!) { + createFavoriteForPerson(data: $data) { + id + person { + id + firstName + lastName + displayName + } + } + } +`; + +export const INSERT_COMPANY_FAVORITE = gql` + mutation InsertCompanyFavorite($data: FavoriteMutationForCompanyArgs!) { + createFavoriteForCompany(data: $data) { + id + company { + id + name + domainName + } + } + } +`; + +export const DELETE_FAVORITE = gql` + mutation DeleteFavorite($where: FavoriteWhereInput!) { + deleteFavorite(where: $where) { + id + } + } +`; diff --git a/front/src/modules/people/components/PeopleCard.tsx b/front/src/modules/people/components/PeopleCard.tsx index 71b38a47d..94226d336 100644 --- a/front/src/modules/people/components/PeopleCard.tsx +++ b/front/src/modules/people/components/PeopleCard.tsx @@ -4,14 +4,17 @@ import styled from '@emotion/styled'; import { Avatar } from '@/users/components/Avatar'; import { Person } from '~/generated/graphql'; -export type PeopleCardPropsType = { +export type PeopleCardProps = { person: Pick; + hasBottomBorder?: boolean; }; -const StyledCard = styled.div` +const StyledCard = styled.div<{ hasBottomBorder: boolean }>` align-items: center; align-self: stretch; - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + border-bottom: 1px solid + ${({ theme, hasBottomBorder }) => + hasBottomBorder ? theme.border.color.light : 'transparent'}; display: flex; gap: ${({ theme }) => theme.spacing(2)}; height: ${({ theme }) => theme.spacing(8)}; @@ -49,10 +52,16 @@ const StyledJobTitle = styled.div` } `; -export function PeopleCard({ person }: PeopleCardPropsType) { +export function PeopleCard({ + person, + hasBottomBorder = true, +}: PeopleCardProps) { const navigate = useNavigate(); return ( - navigate(`/person/${person.id}`)}> + navigate(`/person/${person.id}`)} + hasBottomBorder={hasBottomBorder} + > {person.displayName} - {person.jobTitle ?? 'Add job title'} + {person.jobTitle && {person.jobTitle}} ); diff --git a/front/src/modules/people/constants/peopleViewFields.tsx b/front/src/modules/people/constants/peopleViewFields.tsx index 8fb872b82..fa539e845 100644 --- a/front/src/modules/people/constants/peopleViewFields.tsx +++ b/front/src/modules/people/constants/peopleViewFields.tsx @@ -123,7 +123,7 @@ export const peopleViewFields: ViewFieldDefinition[] = [ } satisfies ViewFieldDefinition, { id: 'x', - columnLabel: 'X', + columnLabel: 'Twitter', columnIcon: , columnSize: 150, columnOrder: 9, diff --git a/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx b/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx index 03292a566..28da420d1 100644 --- a/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx +++ b/front/src/modules/people/editable-field/components/PeopleFullNameEditableField.tsx @@ -48,8 +48,8 @@ export function PeopleFullNameEditableField({ people }: OwnProps) { return ( ))} - + {(draggableProvided) => (
diff --git a/front/src/modules/ui/board/components/NewButton.tsx b/front/src/modules/ui/board/components/NewButton.tsx index fc1714e59..6a96b5484 100644 --- a/front/src/modules/ui/board/components/NewButton.tsx +++ b/front/src/modules/ui/board/components/NewButton.tsx @@ -8,7 +8,7 @@ const StyledButton = styled.button` align-self: baseline; background-color: ${({ theme }) => theme.background.primary}; border: none; - border-radius: ${({ theme }) => theme.border.radius.md}; + border-radius: ${({ theme }) => theme.border.radius.sm}; color: ${({ theme }) => theme.font.color.tertiary}; cursor: pointer; display: flex; diff --git a/front/src/modules/ui/board/states/FieldDefinitionContext.ts b/front/src/modules/ui/board/states/FieldDefinitionContext.ts index c5fb7dc36..0a7fb8b2a 100644 --- a/front/src/modules/ui/board/states/FieldDefinitionContext.ts +++ b/front/src/modules/ui/board/states/FieldDefinitionContext.ts @@ -1,9 +1,14 @@ import { createContext } from 'react'; -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '../../editable-field/types/ViewField'; +import { FieldDefinition } from '@/ui/editable-field/types/FieldDefinition'; +import { FieldMetadata } from '@/ui/editable-field/types/FieldMetadata'; -export const FieldDefinitionContext = - createContext | null>(null); +export const FieldDefinitionContext = createContext< + FieldDefinition +>({ + id: '', + label: '', + icon: undefined, + type: '', + metadata: {} as FieldMetadata, +}); diff --git a/front/src/modules/ui/board/states/fieldsDefinitionsState.ts b/front/src/modules/ui/board/states/viewFieldsDefinitionsState.ts similarity index 70% rename from front/src/modules/ui/board/states/fieldsDefinitionsState.ts rename to front/src/modules/ui/board/states/viewFieldsDefinitionsState.ts index 6fd293646..c3f40224a 100644 --- a/front/src/modules/ui/board/states/fieldsDefinitionsState.ts +++ b/front/src/modules/ui/board/states/viewFieldsDefinitionsState.ts @@ -5,9 +5,9 @@ import type { ViewFieldMetadata, } from '../../editable-field/types/ViewField'; -export const fieldsDefinitionsState = atom< +export const viewFieldsDefinitionsState = atom< ViewFieldDefinition[] >({ - key: 'fieldsDefinitionState', + key: 'viewFieldsDefinitionState', default: [], }); diff --git a/front/src/modules/ui/button/components/IconButton.tsx b/front/src/modules/ui/button/components/IconButton.tsx index 2a3a67b87..c5a02d6f1 100644 --- a/front/src/modules/ui/button/components/IconButton.tsx +++ b/front/src/modules/ui/button/components/IconButton.tsx @@ -5,7 +5,11 @@ export type IconButtonVariant = 'transparent' | 'border' | 'shadow' | 'white'; export type IconButtonSize = 'large' | 'medium' | 'small'; -export type IconButtonFontColor = 'primary' | 'secondary' | 'tertiary'; +export type IconButtonFontColor = + | 'primary' + | 'secondary' + | 'tertiary' + | 'danger'; export type ButtonProps = { icon?: React.ReactNode; @@ -71,7 +75,9 @@ const StyledIconButton = styled.button< return theme.font.color.extraLight; } - return theme.font.color[textColor ?? 'secondary']; + return textColor === 'danger' + ? theme.color.red + : theme.font.color[textColor ?? 'secondary']; }}; cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; display: flex; diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx index 5d9c56155..ca1d779c9 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableDateField.tsx @@ -1,47 +1,40 @@ import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; -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 { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { FieldContext } from '../states/FieldContext'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldDateMetadata } from '../types/FieldMetadata'; import { EditableField } from './EditableField'; +import { GenericEditableDateFieldDisplayMode } from './GenericEditableDateFieldDisplayMode'; import { GenericEditableDateFieldEditMode } from './GenericEditableDateFieldEditMode'; -type OwnProps = { - viewField: ViewFieldDefinition; -}; - -export function GenericEditableDateField({ viewField }: OwnProps) { +export function GenericEditableDateField() { const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; const fieldValue = useRecoilValue( genericEntityFieldFamilySelector({ entityId: currentEditableFieldEntityId ?? '', - fieldName: viewField.metadata.fieldName, + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', }), ); - const internalDateValue = fieldValue - ? parseDate(fieldValue).toJSDate() - : null; - return ( - } - displayModeContent={} + iconLabel={currentEditableFieldDefinition.icon} + editModeContent={} + displayModeContent={} isDisplayModeContentEmpty={!fieldValue} /> diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldDisplayMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldDisplayMode.tsx new file mode 100644 index 000000000..603bfe316 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldDisplayMode.tsx @@ -0,0 +1,33 @@ +import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay'; +import { parseDate } from '~/utils/date-utils'; + +import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; +import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldDateMetadata } from '../types/FieldMetadata'; + +export function GenericEditableDateFieldDisplayMode() { + const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; + + const fieldValue = useRecoilValue( + genericEntityFieldFamilySelector({ + entityId: currentEditableFieldEntityId ?? '', + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', + }), + ); + + const internalDateValue = fieldValue + ? parseDate(fieldValue).toJSDate() + : null; + + return ; +} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx index 8d3b256d7..ed7042669 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableDateFieldEditMode.tsx @@ -1,28 +1,27 @@ import { useContext } from 'react'; import { useRecoilState } from 'recoil'; -import { - ViewFieldDateMetadata, - ViewFieldDefinition, -} from '@/ui/editable-field/types/ViewField'; - import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; +import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldDateMetadata } from '../types/FieldMetadata'; import { EditableFieldEditModeDate } from '../variants/components/EditableFieldEditModeDate'; -type OwnProps = { - viewField: ViewFieldDefinition; -}; - -export function GenericEditableDateFieldEditMode({ viewField }: OwnProps) { +export function GenericEditableDateFieldEditMode() { const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; // TODO: we could use a hook that would return the field value with the right type const [fieldValue, setFieldValue] = useRecoilState( genericEntityFieldFamilySelector({ entityId: currentEditableFieldEntityId ?? '', - fieldName: viewField.metadata.fieldName, + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', }), ); @@ -34,7 +33,11 @@ export function GenericEditableDateFieldEditMode({ viewField }: OwnProps) { setFieldValue(newDateISO); if (currentEditableFieldEntityId && updateField) { - updateField(currentEditableFieldEntityId, viewField, newDateISO); + updateField( + currentEditableFieldEntityId, + currentEditableFieldDefinition, + newDateISO, + ); } } diff --git a/front/src/modules/ui/editable-field/components/GenericEditableField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableField.tsx index 70d639a80..c70881722 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableField.tsx @@ -1,34 +1,30 @@ -import { - ViewFieldDefinition, - ViewFieldMetadata, -} from '@/ui/editable-field/types/ViewField'; +import { useContext } from 'react'; -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 { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; +import { isFieldDate } from '../types/guards/isFieldDate'; +import { isFieldNumber } from '../types/guards/isFieldNumber'; +import { isFieldProbability } from '../types/guards/isFieldProbability'; +import { isFieldRelation } from '../types/guards/isFieldRelation'; import { GenericEditableDateField } from './GenericEditableDateField'; import { GenericEditableNumberField } from './GenericEditableNumberField'; import { GenericEditableRelationField } from './GenericEditableRelationField'; import { ProbabilityEditableField } from './ProbabilityEditableField'; -type OwnProps = { - viewField: ViewFieldDefinition; -}; +export function GenericEditableField() { + const fieldDefinition = useContext(EditableFieldDefinitionContext); -export function GenericEditableField({ viewField: fieldDefinition }: OwnProps) { - if (isViewFieldDate(fieldDefinition)) { - return ; - } else if (isViewFieldNumber(fieldDefinition)) { - return ; - } else if (isViewFieldRelation(fieldDefinition)) { - return ; - } else if (isViewFieldProbability(fieldDefinition)) { - return ; + if (isFieldRelation(fieldDefinition)) { + return ; + } else if (isFieldDate(fieldDefinition)) { + return ; + } else if (isFieldNumber(fieldDefinition)) { + return ; + } else if (isFieldProbability(fieldDefinition)) { + return ; } else { console.warn( - `Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableField`, + `Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableCell`, ); return <>; } diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx index 252352e4b..c59f1e501 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableNumberField.tsx @@ -1,40 +1,38 @@ import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; -import { - ViewFieldDefinition, - ViewFieldNumberMetadata, -} from '@/ui/editable-field/types/ViewField'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; +import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { FieldContext } from '../states/FieldContext'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldNumberMetadata } from '../types/FieldMetadata'; import { EditableField } from './EditableField'; import { GenericEditableNumberFieldEditMode } from './GenericEditableNumberFieldEditMode'; -type OwnProps = { - viewField: ViewFieldDefinition; -}; - -export function GenericEditableNumberField({ viewField }: OwnProps) { +export function GenericEditableNumberField() { const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; const fieldValue = useRecoilValue( genericEntityFieldFamilySelector({ entityId: currentEditableFieldEntityId ?? '', - fieldName: viewField.metadata.fieldName, + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', }), ); return ( - } + iconLabel={currentEditableFieldDefinition.icon} + editModeContent={} displayModeContent={fieldValue} isDisplayModeContentEmpty={!fieldValue} /> diff --git a/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx index 35afc59c5..4ce2ec326 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableNumberFieldEditMode.tsx @@ -1,10 +1,6 @@ import { useContext, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; -import { - ViewFieldDefinition, - ViewFieldNumberMetadata, -} from '@/ui/editable-field/types/ViewField'; import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit'; import { canBeCastAsIntegerOrNull, @@ -13,21 +9,25 @@ import { import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers'; import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; +import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldNumberMetadata } from '../types/FieldMetadata'; -type OwnProps = { - viewField: ViewFieldDefinition; -}; - -export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) { +export function GenericEditableNumberFieldEditMode() { const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; // TODO: we could use a hook that would return the field value with the right type const [fieldValue, setFieldValue] = useRecoilState( genericEntityFieldFamilySelector({ entityId: currentEditableFieldEntityId ?? '', - fieldName: viewField.metadata.fieldName, + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', }), ); const [internalValue, setInternalValue] = useState( @@ -36,6 +36,10 @@ export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) { const updateField = useUpdateGenericEntityField(); + const wrapperRef = useRef(null); + + useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel); + function handleSubmit() { if (!canBeCastAsIntegerOrNull(internalValue)) { return; @@ -47,7 +51,7 @@ export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) { if (currentEditableFieldEntityId && updateField) { updateField( currentEditableFieldEntityId, - viewField, + currentEditableFieldDefinition, castAsIntegerOrNull(internalValue), ); } @@ -60,9 +64,6 @@ export function GenericEditableNumberFieldEditMode({ viewField }: OwnProps) { function handleChange(newValue: string) { setInternalValue(newValue); } - const wrapperRef = useRef(null); - - useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel); return (
diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx index a52f9ecf3..ae2c42ec2 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationField.tsx @@ -1,58 +1,32 @@ import { useContext } from 'react'; import { useRecoilValue } from 'recoil'; -import { PersonChip } from '@/people/components/PersonChip'; -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 { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { FieldContext } from '../states/FieldContext'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldRelationMetadata } from '../types/FieldMetadata'; import { EditableField } from './EditableField'; +import { GenericEditableRelationFieldDisplayMode } from './GenericEditableRelationFieldDisplayMode'; import { GenericEditableRelationFieldEditMode } from './GenericEditableRelationFieldEditMode'; -type OwnProps = { - viewField: ViewFieldDefinition; -}; - -function RelationChip({ - fieldDefinition, - fieldValue, -}: { - fieldDefinition: ViewFieldDefinition; - fieldValue: any | null; -}) { - switch (fieldDefinition.metadata.relationType) { - case Entity.Person: { - return ( - - ); - } - default: - console.warn( - `Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`, - ); - return <> ; - } -} - -export function GenericEditableRelationField({ viewField }: OwnProps) { +export function GenericEditableRelationField() { const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; const fieldValue = useRecoilValue( genericEntityFieldFamilySelector({ entityId: currentEditableFieldEntityId ?? '', - fieldName: viewField.metadata.fieldName, + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', }), ); @@ -64,13 +38,9 @@ export function GenericEditableRelationField({ viewField }: OwnProps) { customEditHotkeyScope={{ scope: RelationPickerHotkeyScope.RelationPicker, }} - iconLabel={viewField.columnIcon} - editModeContent={ - - } - displayModeContent={ - - } + iconLabel={currentEditableFieldDefinition.icon} + editModeContent={} + displayModeContent={} isDisplayModeContentEmpty={!fieldValue} isDisplayModeFixHeight /> diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldDisplayMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldDisplayMode.tsx new file mode 100644 index 000000000..67d6b9369 --- /dev/null +++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldDisplayMode.tsx @@ -0,0 +1,45 @@ +import { useContext } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { PersonChip } from '@/people/components/PersonChip'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; + +import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; +import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; +import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldRelationMetadata } from '../types/FieldMetadata'; + +export function GenericEditableRelationFieldDisplayMode() { + const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; + + const fieldValue = useRecoilValue( + genericEntityFieldFamilySelector({ + entityId: currentEditableFieldEntityId ?? '', + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', + }), + ); + + switch (currentEditableFieldDefinition.metadata.relationType) { + case Entity.Person: { + return ( + + ); + } + default: + console.warn( + `Unknown relation type: "${currentEditableFieldDefinition.metadata.relationType}" + in GenericEditableRelationField`, + ); + return <> ; + } +} diff --git a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx index ce00ba74b..9b4c08c4f 100644 --- a/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx +++ b/front/src/modules/ui/editable-field/components/GenericEditableRelationFieldEditMode.tsx @@ -3,18 +3,17 @@ import styled from '@emotion/styled'; import { useRecoilState } from 'recoil'; import { PeoplePicker } from '@/people/components/PeoplePicker'; -import { - ViewFieldDefinition, - ViewFieldRelationMetadata, - ViewFieldRelationValue, -} from '@/ui/editable-field/types/ViewField'; +import { 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 { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldRelationMetadata } from '../types/FieldMetadata'; const RelationPickerContainer = styled.div` left: 0px; @@ -22,17 +21,13 @@ const RelationPickerContainer = styled.div` top: -8px; `; -type OwnProps = { - viewField: ViewFieldDefinition; -}; - function RelationPicker({ fieldDefinition, fieldValue, handleEntitySubmit, handleCancel, }: { - fieldDefinition: ViewFieldDefinition; + fieldDefinition: FieldDefinition; fieldValue: ViewFieldRelationValue; handleEntitySubmit: (newRelationId: EntityForSelect | null) => void; handleCancel: () => void; @@ -55,14 +50,19 @@ function RelationPicker({ } } -export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) { +export function GenericEditableRelationFieldEditMode() { const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; // TODO: we could use a hook that would return the field value with the right type const [fieldValue, setFieldValue] = useRecoilState( genericEntityFieldFamilySelector({ entityId: currentEditableFieldEntityId ?? '', - fieldName: viewField.metadata.fieldName, + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', }), ); @@ -79,7 +79,11 @@ export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) { }); if (currentEditableFieldEntityId && updateField) { - updateField(currentEditableFieldEntityId, viewField, newRelation); + updateField( + currentEditableFieldEntityId, + currentEditableFieldDefinition, + newRelation, + ); } closeEditableField(); @@ -92,7 +96,7 @@ export function GenericEditableRelationFieldEditMode({ viewField }: OwnProps) { return ( ; -}; +export function ProbabilityEditableField() { + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; -export function ProbabilityEditableField({ viewField }: OwnProps) { return ( - } + iconLabel={currentEditableFieldDefinition.icon} + displayModeContent={} displayModeContentOnly disableHoverEffect /> diff --git a/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx b/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx index e41c01088..dd91c4b2d 100644 --- a/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx +++ b/front/src/modules/ui/editable-field/components/ProbabilityEditableFieldEditMode.tsx @@ -5,12 +5,11 @@ import { useRecoilState } from 'recoil'; import { useEditableField } from '@/ui/editable-field/hooks/useEditableField'; import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; +import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; -import { - ViewFieldDefinition, - ViewFieldProbabilityMetadata, -} from '../types/ViewField'; +import { FieldDefinition } from '../types/FieldDefinition'; +import { FieldProbabilityMetadata } from '../types/FieldMetadata'; const StyledContainer = styled.div` align-items: center; @@ -60,10 +59,6 @@ const StyledLabel = styled.div` width: ${({ theme }) => theme.spacing(12)}; `; -type OwnProps = { - viewField: ViewFieldDefinition; -}; - const PROBABILITY_VALUES = [ { label: '0%', value: 0 }, { label: '25%', value: 25 }, @@ -72,28 +67,38 @@ const PROBABILITY_VALUES = [ { label: '100%', value: 100 }, ]; -export function ProbabilityEditableFieldEditMode({ viewField }: OwnProps) { +export function ProbabilityEditableFieldEditMode() { const [nextProbabilityIndex, setNextProbabilityIndex] = useState< number | null >(null); const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext); + const currentEditableFieldDefinition = useContext( + EditableFieldDefinitionContext, + ) as FieldDefinition; const [fieldValue, setFieldValue] = useRecoilState( genericEntityFieldFamilySelector({ entityId: currentEditableFieldEntityId ?? '', - fieldName: viewField.metadata.fieldName, + fieldName: currentEditableFieldDefinition + ? currentEditableFieldDefinition.metadata.fieldName + : '', }), ); - const probabilityIndex = Math.ceil(fieldValue / 25); const { closeEditableField } = useEditableField(); const updateField = useUpdateGenericEntityField(); + const probabilityIndex = Math.ceil(fieldValue / 25); + function handleChange(newValue: number) { setFieldValue(newValue); if (currentEditableFieldEntityId && updateField) { - updateField(currentEditableFieldEntityId, viewField, newValue); + updateField( + currentEditableFieldEntityId, + currentEditableFieldDefinition, + newValue, + ); } closeEditableField(); } diff --git a/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts b/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts index 87165dd9f..545bc86d7 100644 --- a/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts +++ b/front/src/modules/ui/editable-field/hooks/useUpdateGenericEntityField.ts @@ -1,100 +1,97 @@ import { useContext } from 'react'; -import { isViewFieldChip } from '@/ui/editable-field/types/guards/isViewFieldChip'; -import { EntityUpdateMutationHookContext } from '@/ui/table/states/EntityUpdateMutationHookContext'; +import { isFieldChip } from '@/ui/editable-field/types/guards/isFieldChip'; -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 { EditableFieldMutationContext } from '../states/EditableFieldMutationContext'; +import { FieldDefinition } from '../types/FieldDefinition'; 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'; + FieldChipMetadata, + FieldChipValue, + FieldDateMetadata, + FieldDateValue, + FieldDoubleTextChipMetadata, + FieldDoubleTextChipValue, + FieldDoubleTextMetadata, + FieldDoubleTextValue, + FieldMetadata, + FieldNumberMetadata, + FieldNumberValue, + FieldPhoneMetadata, + FieldPhoneValue, + FieldProbabilityMetadata, + FieldProbabilityValue, + FieldRelationMetadata, + FieldRelationValue, + FieldTextMetadata, + FieldTextValue, + FieldURLMetadata, + FieldURLValue, +} from '../types/FieldMetadata'; +import { isFieldChipValue } from '../types/guards/isFieldChipValue'; +import { isFieldDate } from '../types/guards/isFieldDate'; +import { isFieldDateValue } from '../types/guards/isFieldDateValue'; +import { isFieldDoubleText } from '../types/guards/isFieldDoubleText'; +import { isFieldDoubleTextChip } from '../types/guards/isFieldDoubleTextChip'; +import { isFieldDoubleTextChipValue } from '../types/guards/isFieldDoubleTextChipValue'; +import { isFieldDoubleTextValue } from '../types/guards/isFieldDoubleTextValue'; +import { isFieldNumber } from '../types/guards/isFieldNumber'; +import { isFieldNumberValue } from '../types/guards/isFieldNumberValue'; +import { isFieldPhone } from '../types/guards/isFieldPhone'; +import { isFieldPhoneValue } from '../types/guards/isFieldPhoneValue'; +import { isFieldProbability } from '../types/guards/isFieldProbability'; +import { isFieldProbabilityValue } from '../types/guards/isFieldProbabilityValue'; +import { isFieldRelation } from '../types/guards/isFieldRelation'; +import { isFieldRelationValue } from '../types/guards/isFieldRelationValue'; +import { isFieldText } from '../types/guards/isFieldText'; +import { isFieldTextValue } from '../types/guards/isFieldTextValue'; +import { isFieldURL } from '../types/guards/isFieldURL'; +import { isFieldURLValue } from '../types/guards/isFieldURLValue'; export function useUpdateGenericEntityField() { - const useUpdateEntityMutation = useContext(EntityUpdateMutationHookContext); + const useUpdateEntityMutation = useContext(EditableFieldMutationContext); 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 + return function updateEntityField< + MetadataType extends FieldMetadata, + ValueType extends MetadataType extends FieldDoubleTextMetadata + ? FieldDoubleTextValue + : MetadataType extends FieldTextMetadata + ? FieldTextValue + : MetadataType extends FieldPhoneMetadata + ? FieldPhoneValue + : MetadataType extends FieldURLMetadata + ? FieldURLValue + : MetadataType extends FieldNumberMetadata + ? FieldNumberValue + : MetadataType extends FieldDateMetadata + ? FieldDateValue + : MetadataType extends FieldChipMetadata + ? FieldChipValue + : MetadataType extends FieldDoubleTextChipMetadata + ? FieldDoubleTextChipValue + : MetadataType extends FieldRelationMetadata + ? FieldRelationValue + : MetadataType extends FieldProbabilityMetadata + ? FieldProbabilityValue : unknown, >( currentEntityId: string, - viewField: ViewFieldDefinition, + field: FieldDefinition, 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 ? + // TODO: improve type guards organization, maybe with a common typeguard for all 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 + // The goal would be to check that the field value not only is valid, + // but also that it is validated against the corresponding field type // Relation - if ( - isViewFieldRelation(viewField) && - isViewFieldRelationValue(newFieldValueUnknown) - ) { + if (isFieldRelation(field) && isFieldRelationValue(newFieldValueUnknown)) { const newSelectedEntity = newFieldValueUnknown; - const fieldName = viewField.metadata.fieldName; + const fieldName = field.metadata.fieldName; if (!newSelectedEntity) { updateEntity({ @@ -120,35 +117,29 @@ export function useUpdateGenericEntityField() { }); } // Chip - } else if ( - isViewFieldChip(viewField) && - isViewFieldChipValue(newFieldValueUnknown) - ) { + } else if (isFieldChip(field) && isFieldChipValue(newFieldValueUnknown)) { const newContent = newFieldValueUnknown; updateEntity({ variables: { where: { id: currentEntityId }, - data: { [viewField.metadata.contentFieldName]: newContent }, + data: { [field.metadata.contentFieldName]: newContent }, }, }); // Text - } else if ( - isViewFieldText(viewField) && - isViewFieldTextValue(newFieldValueUnknown) - ) { + } else if (isFieldText(field) && isFieldTextValue(newFieldValueUnknown)) { const newContent = newFieldValueUnknown; updateEntity({ variables: { where: { id: currentEntityId }, - data: { [viewField.metadata.fieldName]: newContent }, + data: { [field.metadata.fieldName]: newContent }, }, }); // Double text } else if ( - isViewFieldDoubleText(viewField) && - isViewFieldDoubleTextValue(newFieldValueUnknown) + isFieldDoubleText(field) && + isFieldDoubleTextValue(newFieldValueUnknown) ) { const newContent = newFieldValueUnknown; @@ -156,15 +147,15 @@ export function useUpdateGenericEntityField() { variables: { where: { id: currentEntityId }, data: { - [viewField.metadata.firstValueFieldName]: newContent.firstValue, - [viewField.metadata.secondValueFieldName]: newContent.secondValue, + [field.metadata.firstValueFieldName]: newContent.firstValue, + [field.metadata.secondValueFieldName]: newContent.secondValue, }, }, }); // Double Text Chip } else if ( - isViewFieldDoubleTextChip(viewField) && - isViewFieldDoubleTextChipValue(newFieldValueUnknown) + isFieldDoubleTextChip(field) && + isFieldDoubleTextChipValue(newFieldValueUnknown) ) { const newContent = newFieldValueUnknown; @@ -172,73 +163,64 @@ export function useUpdateGenericEntityField() { variables: { where: { id: currentEntityId }, data: { - [viewField.metadata.firstValueFieldName]: newContent.firstValue, - [viewField.metadata.secondValueFieldName]: newContent.secondValue, + [field.metadata.firstValueFieldName]: newContent.firstValue, + [field.metadata.secondValueFieldName]: newContent.secondValue, }, }, }); // Phone - } else if ( - isViewFieldPhone(viewField) && - isViewFieldPhoneValue(newFieldValueUnknown) - ) { + } else if (isFieldPhone(field) && isFieldPhoneValue(newFieldValueUnknown)) { const newContent = newFieldValueUnknown; updateEntity({ variables: { where: { id: currentEntityId }, - data: { [viewField.metadata.fieldName]: newContent }, + data: { [field.metadata.fieldName]: newContent }, }, }); // URL - } else if ( - isViewFieldURL(viewField) && - isViewFieldURLValue(newFieldValueUnknown) - ) { + } else if (isFieldURL(field) && isFieldURLValue(newFieldValueUnknown)) { const newContent = newFieldValueUnknown; updateEntity({ variables: { where: { id: currentEntityId }, - data: { [viewField.metadata.fieldName]: newContent }, + data: { [field.metadata.fieldName]: newContent }, }, }); // Number } else if ( - isViewFieldNumber(viewField) && - isViewFieldNumberValue(newFieldValueUnknown) + isFieldNumber(field) && + isFieldNumberValue(newFieldValueUnknown) ) { const newContent = newFieldValueUnknown; updateEntity({ variables: { where: { id: currentEntityId }, - data: { [viewField.metadata.fieldName]: newContent }, + data: { [field.metadata.fieldName]: newContent }, }, }); // Date - } else if ( - isViewFieldDate(viewField) && - isViewFieldDateValue(newFieldValueUnknown) - ) { + } else if (isFieldDate(field) && isFieldDateValue(newFieldValueUnknown)) { const newContent = newFieldValueUnknown; updateEntity({ variables: { where: { id: currentEntityId }, - data: { [viewField.metadata.fieldName]: newContent }, + data: { [field.metadata.fieldName]: newContent }, }, }); } else if ( - isViewFieldProbability(viewField) && - isViewFieldProbabilityValue(newFieldValueUnknown) + isFieldProbability(field) && + isFieldProbabilityValue(newFieldValueUnknown) ) { const newContent = newFieldValueUnknown; updateEntity({ variables: { where: { id: currentEntityId }, - data: { [viewField.metadata.fieldName]: newContent }, + data: { [field.metadata.fieldName]: newContent }, }, }); } diff --git a/front/src/modules/ui/editable-field/states/EditableFieldDefinitionContext.ts b/front/src/modules/ui/editable-field/states/EditableFieldDefinitionContext.ts new file mode 100644 index 000000000..2ce04ca7b --- /dev/null +++ b/front/src/modules/ui/editable-field/states/EditableFieldDefinitionContext.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; + +import { FieldDefinition } from '../types/FieldDefinition'; +import { ViewFieldMetadata } from '../types/ViewField'; + +export const EditableFieldDefinitionContext = createContext< + FieldDefinition +>({} as FieldDefinition); diff --git a/front/src/modules/ui/editable-field/states/EditableFieldEntityIdContext.ts b/front/src/modules/ui/editable-field/states/EditableFieldEntityIdContext.ts index bf6875085..0f1116746 100644 --- a/front/src/modules/ui/editable-field/states/EditableFieldEntityIdContext.ts +++ b/front/src/modules/ui/editable-field/states/EditableFieldEntityIdContext.ts @@ -1,3 +1,3 @@ import { createContext } from 'react'; -export const EditableFieldEntityIdContext = createContext(null); +export const EditableFieldEntityIdContext = createContext(''); diff --git a/front/src/modules/ui/editable-field/states/EditableFieldMutationContext.ts b/front/src/modules/ui/editable-field/states/EditableFieldMutationContext.ts new file mode 100644 index 000000000..6d7b54992 --- /dev/null +++ b/front/src/modules/ui/editable-field/states/EditableFieldMutationContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react'; + +export const EditableFieldMutationContext = createContext(undefined); diff --git a/front/src/modules/ui/editable-field/types/FieldDefinition.ts b/front/src/modules/ui/editable-field/types/FieldDefinition.ts new file mode 100644 index 000000000..0cb858308 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/FieldDefinition.ts @@ -0,0 +1,9 @@ +import { FieldMetadata } from './FieldMetadata'; + +export type FieldDefinition = { + id: string; + label: string; + icon?: JSX.Element; + type: string; + metadata: T; +}; diff --git a/front/src/modules/ui/editable-field/types/FieldMetadata.ts b/front/src/modules/ui/editable-field/types/FieldMetadata.ts new file mode 100644 index 000000000..0b2e598b3 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/FieldMetadata.ts @@ -0,0 +1,113 @@ +import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; +import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; + +export type FieldType = + | 'text' + | 'relation' + | 'chip' + | 'double-text-chip' + | 'double-text' + | 'number' + | 'date' + | 'phone' + | 'url' + | 'probability'; + +export type FieldTextMetadata = { + type: 'text'; + placeHolder: string; + fieldName: string; +}; + +export type FieldPhoneMetadata = { + type: 'phone'; + placeHolder: string; + fieldName: string; +}; + +export type FieldURLMetadata = { + type: 'url'; + placeHolder: string; + fieldName: string; +}; + +export type FieldDateMetadata = { + type: 'date'; + fieldName: string; +}; + +export type FieldNumberMetadata = { + type: 'number'; + fieldName: string; +}; + +export type FieldRelationMetadata = { + type: 'relation'; + relationType: Entity; + fieldName: string; +}; + +export type FieldChipMetadata = { + type: 'chip'; + relationType: Entity; + contentFieldName: string; + urlFieldName: string; + placeHolder: string; +}; + +export type FieldDoubleTextMetadata = { + type: 'double-text'; + firstValueFieldName: string; + firstValuePlaceholder: string; + secondValueFieldName: string; + secondValuePlaceholder: string; +}; + +export type FieldDoubleTextChipMetadata = { + type: 'double-text-chip'; + firstValueFieldName: string; + firstValuePlaceholder: string; + secondValueFieldName: string; + secondValuePlaceholder: string; + avatarUrlFieldName: string; + entityType: Entity; +}; + +export type FieldProbabilityMetadata = { + type: 'probability'; + fieldName: string; +}; + +export type FieldMetadata = { type: FieldType } & ( + | FieldTextMetadata + | FieldRelationMetadata + | FieldChipMetadata + | FieldDoubleTextChipMetadata + | FieldDoubleTextMetadata + | FieldPhoneMetadata + | FieldURLMetadata + | FieldNumberMetadata + | FieldDateMetadata + | FieldProbabilityMetadata +); + +export type FieldTextValue = string; + +export type FieldChipValue = string; +export type FieldDateValue = string; +export type FieldPhoneValue = string; +export type FieldURLValue = string; +export type FieldNumberValue = number | null; +export type FieldProbabilityValue = number; + +export type FieldDoubleTextValue = { + firstValue: string; + secondValue: string; +}; + +export type FieldDoubleTextChipValue = { + firstValue: string; + secondValue: string; +}; + +export type FieldRelationValue = EntityForSelect | null; diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldChip.ts b/front/src/modules/ui/editable-field/types/guards/isFieldChip.ts new file mode 100644 index 000000000..b7e2eac37 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldChip.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldChipMetadata, FieldMetadata } from '../FieldMetadata'; + +export function isFieldChip( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'chip'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldChipValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldChipValue.ts new file mode 100644 index 000000000..649a08fa4 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldChipValue.ts @@ -0,0 +1,12 @@ +import { FieldChipValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldChipValue( + fieldValue: unknown, +): fieldValue is FieldChipValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'string' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDate.ts b/front/src/modules/ui/editable-field/types/guards/isFieldDate.ts new file mode 100644 index 000000000..e6258847e --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldDate.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldDateMetadata, FieldMetadata } from '../FieldMetadata'; + +export function isFieldDate( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'date'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDateValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldDateValue.ts new file mode 100644 index 000000000..1d7bdfd88 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldDateValue.ts @@ -0,0 +1,12 @@ +import { FieldDateValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldDateValue( + fieldValue: unknown, +): fieldValue is FieldDateValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'string' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleText.ts b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleText.ts new file mode 100644 index 000000000..b30c0c6a0 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleText.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldDoubleTextMetadata, FieldMetadata } from '../FieldMetadata'; + +export function isFieldDoubleText( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'double-text'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChip.ts b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChip.ts new file mode 100644 index 000000000..73bc0c545 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChip.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldDoubleTextChipMetadata, FieldMetadata } from '../FieldMetadata'; + +export function isFieldDoubleTextChip( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'double-text-chip'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChipValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChipValue.ts new file mode 100644 index 000000000..b5d7812a3 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextChipValue.ts @@ -0,0 +1,12 @@ +import { FieldDoubleTextChipValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldDoubleTextChipValue( + fieldValue: unknown, +): fieldValue is FieldDoubleTextChipValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'object' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextValue.ts new file mode 100644 index 000000000..9e5047676 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldDoubleTextValue.ts @@ -0,0 +1,12 @@ +import { FieldDoubleTextValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldDoubleTextValue( + fieldValue: unknown, +): fieldValue is FieldDoubleTextValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'object' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldNumber.ts b/front/src/modules/ui/editable-field/types/guards/isFieldNumber.ts new file mode 100644 index 000000000..7c23d6167 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldNumber.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldMetadata, FieldNumberMetadata } from '../FieldMetadata'; + +export function isFieldNumber( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'number'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldNumberValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldNumberValue.ts new file mode 100644 index 000000000..452bdbede --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldNumberValue.ts @@ -0,0 +1,12 @@ +import { FieldNumberValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldNumberValue( + fieldValue: unknown, +): fieldValue is FieldNumberValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'number' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldPhone.ts b/front/src/modules/ui/editable-field/types/guards/isFieldPhone.ts new file mode 100644 index 000000000..e886b9bbc --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldPhone.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldMetadata, FieldPhoneMetadata } from '../FieldMetadata'; + +export function isFieldPhone( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'phone'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldPhoneValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldPhoneValue.ts new file mode 100644 index 000000000..1337385a7 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldPhoneValue.ts @@ -0,0 +1,12 @@ +import { FieldPhoneValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldPhoneValue( + fieldValue: unknown, +): fieldValue is FieldPhoneValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'string' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldProbability.ts b/front/src/modules/ui/editable-field/types/guards/isFieldProbability.ts new file mode 100644 index 000000000..97c12661e --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldProbability.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldMetadata, FieldProbabilityMetadata } from '../FieldMetadata'; + +export function isFieldProbability( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'probability'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldProbabilityValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldProbabilityValue.ts new file mode 100644 index 000000000..d4dad6eb3 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldProbabilityValue.ts @@ -0,0 +1,12 @@ +import { FieldProbabilityValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldProbabilityValue( + fieldValue: unknown, +): fieldValue is FieldProbabilityValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'number' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldRelation.ts b/front/src/modules/ui/editable-field/types/guards/isFieldRelation.ts new file mode 100644 index 000000000..bb24ecac2 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldRelation.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldMetadata, FieldRelationMetadata } from '../FieldMetadata'; + +export function isFieldRelation( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'relation'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldRelationValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldRelationValue.ts new file mode 100644 index 000000000..5624e0c25 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldRelationValue.ts @@ -0,0 +1,12 @@ +import { FieldRelationValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldRelationValue( + fieldValue: unknown, +): fieldValue is FieldRelationValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'object' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldText.ts b/front/src/modules/ui/editable-field/types/guards/isFieldText.ts new file mode 100644 index 000000000..d616dd595 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldText.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldMetadata, FieldTextMetadata } from '../FieldMetadata'; + +export function isFieldText( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'text'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldTextValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldTextValue.ts new file mode 100644 index 000000000..19f958249 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldTextValue.ts @@ -0,0 +1,12 @@ +import { FieldTextValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldTextValue( + fieldValue: unknown, +): fieldValue is FieldTextValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'string' + ); +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldURL.ts b/front/src/modules/ui/editable-field/types/guards/isFieldURL.ts new file mode 100644 index 000000000..5092c760a --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldURL.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from '../FieldDefinition'; +import { FieldMetadata, FieldURLMetadata } from '../FieldMetadata'; + +export function isFieldURL( + field: FieldDefinition, +): field is FieldDefinition { + return field.type === 'url'; +} diff --git a/front/src/modules/ui/editable-field/types/guards/isFieldURLValue.ts b/front/src/modules/ui/editable-field/types/guards/isFieldURLValue.ts new file mode 100644 index 000000000..96afe4154 --- /dev/null +++ b/front/src/modules/ui/editable-field/types/guards/isFieldURLValue.ts @@ -0,0 +1,12 @@ +import { FieldURLValue } from '../FieldMetadata'; + +// TODO: add yup +export function isFieldURLValue( + fieldValue: unknown, +): fieldValue is FieldURLValue { + return ( + fieldValue !== null && + fieldValue !== undefined && + typeof fieldValue === 'string' + ); +} diff --git a/front/src/modules/ui/icon/index.ts b/front/src/modules/ui/icon/index.ts index a8b930519..9a70bbae2 100644 --- a/front/src/modules/ui/icon/index.ts +++ b/front/src/modules/ui/icon/index.ts @@ -51,6 +51,7 @@ export { IconUserCircle } from '@tabler/icons-react'; export { IconCalendar } from '@tabler/icons-react'; export { IconPencil } from '@tabler/icons-react'; export { IconCircleDot } from '@tabler/icons-react'; +export { IconHeart } from '@tabler/icons-react'; export { IconBrandX } from '@tabler/icons-react'; export { IconTag } from '@tabler/icons-react'; export { IconHelpCircle } from '@tabler/icons-react'; diff --git a/front/src/modules/ui/input/checkbox/components/Checkbox.tsx b/front/src/modules/ui/input/checkbox/components/Checkbox.tsx index 4caec5717..a4ec455d7 100644 --- a/front/src/modules/ui/input/checkbox/components/Checkbox.tsx +++ b/front/src/modules/ui/input/checkbox/components/Checkbox.tsx @@ -38,6 +38,7 @@ const StyledInput = styled.input<{ variant: CheckboxVariant; indeterminate?: boolean; shape?: CheckboxShape; + isChecked: boolean; }>` cursor: pointer; margin: 0; @@ -58,10 +59,10 @@ const StyledInput = styled.input<{ & + label:before { --size: ${({ checkboxSize }) => checkboxSize === CheckboxSize.Large ? '18px' : '12px'}; - background: ${({ theme, indeterminate }) => - indeterminate ? theme.color.blue : 'transparent'}; - border-color: ${({ theme, indeterminate, variant }) => - indeterminate + background: ${({ theme, indeterminate, isChecked }) => + indeterminate || isChecked ? theme.color.blue : 'transparent'}; + border-color: ${({ theme, indeterminate, isChecked, variant }) => + indeterminate || isChecked ? theme.color.blue : variant === CheckboxVariant.Primary ? theme.border.color.inverted @@ -79,11 +80,6 @@ const StyledInput = styled.input<{ width: var(--size); } - &:checked + label:before { - background: ${({ theme }) => theme.color.blue}; - border-color: ${({ theme }) => theme.color.blue}; - } - & + label > svg { --padding: ${({ checkboxSize }) => checkboxSize === CheckboxSize.Large ? '2px' : '1px'}; @@ -112,7 +108,6 @@ export function Checkbox({ React.useEffect(() => { setIsInternalChecked(checked); }, [checked]); - function handleChange(value: boolean) { onChange?.(value); setIsInternalChecked(!isInternalChecked); @@ -130,6 +125,7 @@ export function Checkbox({ variant={variant} checkboxSize={size} shape={shape} + isChecked={isInternalChecked} onChange={(event) => handleChange(event.target.checked)} />