1721/feature/drag and drop favorites (#2097)
* prisma schema updated: added index in favorite * update abilitiy added for favorite * update one favorite resolver added * update on favorite mutation added * updateFavoriteOrder added * Draglist added in favorite * nav item convert to div from button: because it was not working dragable with button * changed index to position * position added in getFavorites query * added recoil state for favorites * reordering updated according to new method * Use accurate type --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -395,6 +395,7 @@ export type Favorite = {
|
|||||||
id: Scalars['ID']['output'];
|
id: Scalars['ID']['output'];
|
||||||
person?: Maybe<Person>;
|
person?: Maybe<Person>;
|
||||||
personId?: Maybe<Scalars['String']['output']>;
|
personId?: Maybe<Scalars['String']['output']>;
|
||||||
|
position: Scalars['Float']['output'];
|
||||||
workspaceId?: Maybe<Scalars['String']['output']>;
|
workspaceId?: Maybe<Scalars['String']['output']>;
|
||||||
workspaceMember?: Maybe<WorkspaceMember>;
|
workspaceMember?: Maybe<WorkspaceMember>;
|
||||||
workspaceMemberId?: Maybe<Scalars['String']['output']>;
|
workspaceMemberId?: Maybe<Scalars['String']['output']>;
|
||||||
|
|||||||
@ -1226,6 +1226,7 @@ export type Favorite = {
|
|||||||
id: Scalars['ID'];
|
id: Scalars['ID'];
|
||||||
person?: Maybe<Person>;
|
person?: Maybe<Person>;
|
||||||
personId?: Maybe<Scalars['String']>;
|
personId?: Maybe<Scalars['String']>;
|
||||||
|
position: Scalars['Float'];
|
||||||
workspaceId?: Maybe<Scalars['String']>;
|
workspaceId?: Maybe<Scalars['String']>;
|
||||||
workspaceMember?: Maybe<WorkspaceMember>;
|
workspaceMember?: Maybe<WorkspaceMember>;
|
||||||
workspaceMemberId?: Maybe<Scalars['String']>;
|
workspaceMemberId?: Maybe<Scalars['String']>;
|
||||||
@ -1247,16 +1248,24 @@ export type FavoriteListRelationFilter = {
|
|||||||
|
|
||||||
export type FavoriteMutationForCompanyArgs = {
|
export type FavoriteMutationForCompanyArgs = {
|
||||||
companyId: Scalars['String'];
|
companyId: Scalars['String'];
|
||||||
|
position: Scalars['Float'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FavoriteMutationForPersonArgs = {
|
export type FavoriteMutationForPersonArgs = {
|
||||||
personId: Scalars['String'];
|
personId: Scalars['String'];
|
||||||
|
position: Scalars['Float'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FavoriteOrderByRelationAggregateInput = {
|
export type FavoriteOrderByRelationAggregateInput = {
|
||||||
_count?: InputMaybe<SortOrder>;
|
_count?: InputMaybe<SortOrder>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FavoriteUpdateInput = {
|
||||||
|
id?: InputMaybe<Scalars['String']>;
|
||||||
|
position?: InputMaybe<Scalars['Float']>;
|
||||||
|
workspaceId?: InputMaybe<Scalars['String']>;
|
||||||
|
};
|
||||||
|
|
||||||
export type FavoriteUpdateManyWithoutCompanyNestedInput = {
|
export type FavoriteUpdateManyWithoutCompanyNestedInput = {
|
||||||
connect?: InputMaybe<Array<FavoriteWhereUniqueInput>>;
|
connect?: InputMaybe<Array<FavoriteWhereUniqueInput>>;
|
||||||
disconnect?: InputMaybe<Array<FavoriteWhereUniqueInput>>;
|
disconnect?: InputMaybe<Array<FavoriteWhereUniqueInput>>;
|
||||||
@ -1282,6 +1291,7 @@ export type FavoriteWhereInput = {
|
|||||||
companyId?: InputMaybe<StringNullableFilter>;
|
companyId?: InputMaybe<StringNullableFilter>;
|
||||||
id?: InputMaybe<StringFilter>;
|
id?: InputMaybe<StringFilter>;
|
||||||
personId?: InputMaybe<StringNullableFilter>;
|
personId?: InputMaybe<StringNullableFilter>;
|
||||||
|
position?: InputMaybe<FloatFilter>;
|
||||||
workspaceId?: InputMaybe<StringNullableFilter>;
|
workspaceId?: InputMaybe<StringNullableFilter>;
|
||||||
workspaceMemberId?: InputMaybe<StringNullableFilter>;
|
workspaceMemberId?: InputMaybe<StringNullableFilter>;
|
||||||
};
|
};
|
||||||
@ -1413,6 +1423,7 @@ export type Mutation = {
|
|||||||
signUp: LoginToken;
|
signUp: LoginToken;
|
||||||
updateOneActivity: Activity;
|
updateOneActivity: Activity;
|
||||||
updateOneCompany?: Maybe<Company>;
|
updateOneCompany?: Maybe<Company>;
|
||||||
|
updateOneFavorites: Favorite;
|
||||||
updateOneField: Field;
|
updateOneField: Field;
|
||||||
updateOneObject: Object;
|
updateOneObject: Object;
|
||||||
updateOnePerson?: Maybe<Person>;
|
updateOnePerson?: Maybe<Person>;
|
||||||
@ -1637,6 +1648,12 @@ export type MutationUpdateOneCompanyArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationUpdateOneFavoritesArgs = {
|
||||||
|
data: FavoriteUpdateInput;
|
||||||
|
where: FavoriteWhereUniqueInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationUpdateOnePersonArgs = {
|
export type MutationUpdateOnePersonArgs = {
|
||||||
data: PersonUpdateInput;
|
data: PersonUpdateInput;
|
||||||
where: PersonWhereUniqueInput;
|
where: PersonWhereUniqueInput;
|
||||||
@ -3854,10 +3871,18 @@ export type InsertPersonFavoriteMutationVariables = Exact<{
|
|||||||
|
|
||||||
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 InsertPersonFavoriteMutation = { __typename?: 'Mutation', createFavoriteForPerson: { __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string, firstName?: string | null, lastName?: string | null, displayName: string } | null } };
|
||||||
|
|
||||||
|
export type UpdateOneFavoriteMutationVariables = Exact<{
|
||||||
|
data: FavoriteUpdateInput;
|
||||||
|
where: FavoriteWhereUniqueInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type UpdateOneFavoriteMutation = { __typename?: 'Mutation', updateOneFavorites: { __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 GetFavoritesQueryVariables = Exact<{ [key: string]: never; }>;
|
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 GetFavoritesQuery = { __typename?: 'Query', findFavorites: Array<{ __typename?: 'Favorite', id: string, position: number, 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 BasePersonFieldsFragmentFragment = { __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null, createdAt: string };
|
export type BasePersonFieldsFragmentFragment = { __typename?: 'Person', id: string, phone?: string | null, email?: string | null, city?: string | null, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null, createdAt: string };
|
||||||
|
|
||||||
@ -5589,10 +5614,61 @@ export function useInsertPersonFavoriteMutation(baseOptions?: Apollo.MutationHoo
|
|||||||
export type InsertPersonFavoriteMutationHookResult = ReturnType<typeof useInsertPersonFavoriteMutation>;
|
export type InsertPersonFavoriteMutationHookResult = ReturnType<typeof useInsertPersonFavoriteMutation>;
|
||||||
export type InsertPersonFavoriteMutationResult = Apollo.MutationResult<InsertPersonFavoriteMutation>;
|
export type InsertPersonFavoriteMutationResult = Apollo.MutationResult<InsertPersonFavoriteMutation>;
|
||||||
export type InsertPersonFavoriteMutationOptions = Apollo.BaseMutationOptions<InsertPersonFavoriteMutation, InsertPersonFavoriteMutationVariables>;
|
export type InsertPersonFavoriteMutationOptions = Apollo.BaseMutationOptions<InsertPersonFavoriteMutation, InsertPersonFavoriteMutationVariables>;
|
||||||
|
export const UpdateOneFavoriteDocument = gql`
|
||||||
|
mutation UpdateOneFavorite($data: FavoriteUpdateInput!, $where: FavoriteWhereUniqueInput!) {
|
||||||
|
updateOneFavorites(data: $data, where: $where) {
|
||||||
|
id
|
||||||
|
person {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
company {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
domainName
|
||||||
|
accountOwner {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type UpdateOneFavoriteMutationFn = Apollo.MutationFunction<UpdateOneFavoriteMutation, UpdateOneFavoriteMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useUpdateOneFavoriteMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useUpdateOneFavoriteMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useUpdateOneFavoriteMutation` 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 [updateOneFavoriteMutation, { data, loading, error }] = useUpdateOneFavoriteMutation({
|
||||||
|
* variables: {
|
||||||
|
* data: // value for 'data'
|
||||||
|
* where: // value for 'where'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useUpdateOneFavoriteMutation(baseOptions?: Apollo.MutationHookOptions<UpdateOneFavoriteMutation, UpdateOneFavoriteMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<UpdateOneFavoriteMutation, UpdateOneFavoriteMutationVariables>(UpdateOneFavoriteDocument, options);
|
||||||
|
}
|
||||||
|
export type UpdateOneFavoriteMutationHookResult = ReturnType<typeof useUpdateOneFavoriteMutation>;
|
||||||
|
export type UpdateOneFavoriteMutationResult = Apollo.MutationResult<UpdateOneFavoriteMutation>;
|
||||||
|
export type UpdateOneFavoriteMutationOptions = Apollo.BaseMutationOptions<UpdateOneFavoriteMutation, UpdateOneFavoriteMutationVariables>;
|
||||||
export const GetFavoritesDocument = gql`
|
export const GetFavoritesDocument = gql`
|
||||||
query GetFavorites {
|
query GetFavorites {
|
||||||
findFavorites {
|
findFavorites {
|
||||||
id
|
id
|
||||||
|
position
|
||||||
person {
|
person {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||||
|
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||||
import NavItem from '@/ui/navigation/navbar/components/NavItem';
|
import NavItem from '@/ui/navigation/navbar/components/NavItem';
|
||||||
import NavTitle from '@/ui/navigation/navbar/components/NavTitle';
|
import NavTitle from '@/ui/navigation/navbar/components/NavTitle';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { useGetFavoritesQuery } from '~/generated/graphql';
|
import { useGetFavoritesQuery } from '~/generated/graphql';
|
||||||
import { getLogoUrlFromDomainName } from '~/utils';
|
import { getLogoUrlFromDomainName } from '~/utils';
|
||||||
|
|
||||||
|
import { useFavorites } from '../hooks/useFavorites';
|
||||||
|
import { favoritesState } from '../states/favoritesState';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -14,46 +20,94 @@ const StyledContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const Favorites = () => {
|
export const Favorites = () => {
|
||||||
const { data } = useGetFavoritesQuery();
|
const [favorites, setFavorites] = useRecoilState(favoritesState);
|
||||||
const favorites = data?.findFavorites;
|
const { handleReorderFavorite } = useFavorites();
|
||||||
|
|
||||||
|
useGetFavoritesQuery({
|
||||||
|
onCompleted: (data) =>
|
||||||
|
setFavorites(
|
||||||
|
data?.findFavorites.map((favorite) => {
|
||||||
|
return {
|
||||||
|
id: favorite.id,
|
||||||
|
person: favorite.person
|
||||||
|
? {
|
||||||
|
id: favorite.person.id,
|
||||||
|
firstName: favorite.person.firstName,
|
||||||
|
lastName: favorite.person.lastName,
|
||||||
|
avatarUrl: favorite.person.avatarUrl,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
company: favorite.company
|
||||||
|
? {
|
||||||
|
id: favorite.company.id,
|
||||||
|
name: favorite.company.name,
|
||||||
|
domainName: favorite.company.domainName,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
position: favorite.position,
|
||||||
|
};
|
||||||
|
}) ?? [],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
if (!favorites || favorites.length === 0) return <></>;
|
if (!favorites || favorites.length === 0) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<NavTitle label="Favorites" />
|
<NavTitle label="Favorites" />
|
||||||
{favorites.map(
|
<DraggableList
|
||||||
({ id, person, company }) =>
|
onDragEnd={handleReorderFavorite}
|
||||||
(person && (
|
draggableItems={
|
||||||
<NavItem
|
<>
|
||||||
key={id}
|
{favorites.map((favorite, index) => {
|
||||||
label={`${person.firstName} ${person.lastName}`}
|
const { id, person, company } = favorite;
|
||||||
Icon={() => (
|
return (
|
||||||
<Avatar
|
<DraggableItem
|
||||||
colorId={person.id}
|
key={id}
|
||||||
avatarUrl={person.avatarUrl ?? ''}
|
draggableId={id}
|
||||||
type="rounded"
|
index={index}
|
||||||
placeholder={`${person.firstName} ${person.lastName}`}
|
itemComponent={
|
||||||
|
<>
|
||||||
|
{person && (
|
||||||
|
<NavItem
|
||||||
|
key={id}
|
||||||
|
label={`${person.firstName} ${person.lastName}`}
|
||||||
|
Icon={() => (
|
||||||
|
<Avatar
|
||||||
|
colorId={person.id}
|
||||||
|
avatarUrl={person.avatarUrl ?? ''}
|
||||||
|
type="rounded"
|
||||||
|
placeholder={`${person.firstName} ${person.lastName}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
to={`/person/${person.id}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{company && (
|
||||||
|
<NavItem
|
||||||
|
key={id}
|
||||||
|
label={company.name}
|
||||||
|
Icon={() => (
|
||||||
|
<Avatar
|
||||||
|
avatarUrl={
|
||||||
|
getLogoUrlFromDomainName(company.domainName) ??
|
||||||
|
''
|
||||||
|
}
|
||||||
|
type="squared"
|
||||||
|
placeholder={company.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
to={`/companies/${company.id}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
);
|
||||||
to={`/person/${person.id}`}
|
})}
|
||||||
/>
|
</>
|
||||||
)) ??
|
}
|
||||||
(company && (
|
/>
|
||||||
<NavItem
|
|
||||||
key={id}
|
|
||||||
label={company.name}
|
|
||||||
Icon={() => (
|
|
||||||
<Avatar
|
|
||||||
avatarUrl={getLogoUrlFromDomainName(company.domainName) ?? ''}
|
|
||||||
type="squared"
|
|
||||||
placeholder={company.name}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
to={`/companies/${company.id}`}
|
|
||||||
/>
|
|
||||||
)),
|
|
||||||
)}
|
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const UPDATE_FAVORITE = gql`
|
||||||
|
mutation UpdateOneFavorite(
|
||||||
|
$data: FavoriteUpdateInput!
|
||||||
|
$where: FavoriteWhereUniqueInput!
|
||||||
|
) {
|
||||||
|
updateOneFavorites(data: $data, where: $where) {
|
||||||
|
id
|
||||||
|
person {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
company {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
domainName
|
||||||
|
accountOwner {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -4,6 +4,7 @@ export const GET_FAVORITES = gql`
|
|||||||
query GetFavorites {
|
query GetFavorites {
|
||||||
findFavorites {
|
findFavorites {
|
||||||
id
|
id
|
||||||
|
position
|
||||||
person {
|
person {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
|
|||||||
@ -1,25 +1,34 @@
|
|||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { GET_COMPANY } from '@/companies/graphql/queries/getCompany';
|
import { GET_COMPANY } from '@/companies/graphql/queries/getCompany';
|
||||||
import { GET_PERSON } from '@/people/graphql/queries/getPerson';
|
import { GET_PERSON } from '@/people/graphql/queries/getPerson';
|
||||||
import {
|
import {
|
||||||
|
Favorite,
|
||||||
useDeleteFavoriteMutation,
|
useDeleteFavoriteMutation,
|
||||||
useInsertCompanyFavoriteMutation,
|
useInsertCompanyFavoriteMutation,
|
||||||
useInsertPersonFavoriteMutation,
|
useInsertPersonFavoriteMutation,
|
||||||
|
useUpdateOneFavoriteMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
import { GET_FAVORITES } from '../graphql/queries/getFavorites';
|
import { GET_FAVORITES } from '../graphql/queries/getFavorites';
|
||||||
|
import { favoritesState } from '../states/favoritesState';
|
||||||
|
|
||||||
export const useFavorites = () => {
|
export const useFavorites = () => {
|
||||||
|
const [favorites, setFavorites] = useRecoilState(favoritesState);
|
||||||
|
|
||||||
const [insertCompanyFavoriteMutation] = useInsertCompanyFavoriteMutation();
|
const [insertCompanyFavoriteMutation] = useInsertCompanyFavoriteMutation();
|
||||||
const [insertPersonFavoriteMutation] = useInsertPersonFavoriteMutation();
|
const [insertPersonFavoriteMutation] = useInsertPersonFavoriteMutation();
|
||||||
const [deleteFavoriteMutation] = useDeleteFavoriteMutation();
|
const [deleteFavoriteMutation] = useDeleteFavoriteMutation();
|
||||||
|
const [updateOneFavoritesMutation] = useUpdateOneFavoriteMutation();
|
||||||
|
|
||||||
const insertCompanyFavorite = (companyId: string) => {
|
const insertCompanyFavorite = (companyId: string) => {
|
||||||
insertCompanyFavoriteMutation({
|
insertCompanyFavoriteMutation({
|
||||||
variables: {
|
variables: {
|
||||||
data: {
|
data: {
|
||||||
companyId,
|
companyId,
|
||||||
|
position: favorites.length + 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
@ -34,6 +43,7 @@ export const useFavorites = () => {
|
|||||||
variables: {
|
variables: {
|
||||||
data: {
|
data: {
|
||||||
personId,
|
personId,
|
||||||
|
position: favorites.length + 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
@ -43,6 +53,25 @@ export const useFavorites = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateFavoritePosition = async (
|
||||||
|
favorites: Pick<Favorite, 'id' | 'position'>,
|
||||||
|
) => {
|
||||||
|
await updateOneFavoritesMutation({
|
||||||
|
variables: {
|
||||||
|
data: {
|
||||||
|
position: favorites?.position,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: favorites.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
refetchQueries: [
|
||||||
|
getOperationName(GET_FAVORITES) ?? '',
|
||||||
|
getOperationName(GET_PERSON) ?? '',
|
||||||
|
getOperationName(GET_COMPANY) ?? '',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
||||||
const deleteCompanyFavorite = (companyId: string) => {
|
const deleteCompanyFavorite = (companyId: string) => {
|
||||||
deleteFavoriteMutation({
|
deleteFavoriteMutation({
|
||||||
variables: {
|
variables: {
|
||||||
@ -75,10 +104,37 @@ export const useFavorites = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const computeNewPosition = (destIndex: number) => {
|
||||||
|
if (destIndex === 0) {
|
||||||
|
return favorites[destIndex].position / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destIndex === favorites.length - 1) {
|
||||||
|
return favorites[destIndex].position + 1;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(favorites[destIndex - 1].position + favorites[destIndex].position) / 2
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReorderFavorite: OnDragEndResponder = (result) => {
|
||||||
|
if (!result.destination || !favorites) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newPosition = computeNewPosition(result.destination.index);
|
||||||
|
|
||||||
|
const reorderFavorites = Array.from(favorites);
|
||||||
|
const [removed] = reorderFavorites.splice(result.source.index, 1);
|
||||||
|
const removedFav = { ...removed, position: newPosition };
|
||||||
|
reorderFavorites.splice(result.destination.index, 0, removedFav);
|
||||||
|
setFavorites(reorderFavorites);
|
||||||
|
updateFavoritePosition(removedFav);
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
insertCompanyFavorite,
|
insertCompanyFavorite,
|
||||||
insertPersonFavorite,
|
insertPersonFavorite,
|
||||||
deleteCompanyFavorite,
|
deleteCompanyFavorite,
|
||||||
deletePersonFavorite,
|
deletePersonFavorite,
|
||||||
|
handleReorderFavorite,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
16
front/src/modules/favorites/states/favoritesState.ts
Normal file
16
front/src/modules/favorites/states/favoritesState.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
import { Company, Favorite, Person } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export const favoritesState = atom<
|
||||||
|
Array<
|
||||||
|
Pick<Favorite, 'id' | 'position'> & {
|
||||||
|
company?: Pick<Company, 'id' | 'name' | 'domainName'>;
|
||||||
|
} & {
|
||||||
|
person?: Pick<Person, 'id' | 'firstName' | 'lastName' | 'avatarUrl'>;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>({
|
||||||
|
key: 'favoritesState',
|
||||||
|
default: [],
|
||||||
|
});
|
||||||
@ -27,7 +27,7 @@ type StyledItemProps = {
|
|||||||
soon?: boolean;
|
soon?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledItem = styled.button<StyledItemProps>`
|
const StyledItem = styled.div<StyledItemProps>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${(props) =>
|
background: ${(props) =>
|
||||||
props.active ? props.theme.background.transparent.light : 'inherit'};
|
props.active ? props.theme.background.transparent.light : 'inherit'};
|
||||||
|
|||||||
@ -201,6 +201,7 @@ export class AbilityFactory {
|
|||||||
// Favorite
|
// Favorite
|
||||||
can(AbilityAction.Read, 'Favorite', { workspaceId: workspace.id });
|
can(AbilityAction.Read, 'Favorite', { workspaceId: workspace.id });
|
||||||
can(AbilityAction.Create, 'Favorite');
|
can(AbilityAction.Create, 'Favorite');
|
||||||
|
can(AbilityAction.Update, 'Favorite', { workspaceId: workspace.id });
|
||||||
can(AbilityAction.Delete, 'Favorite', { workspaceId: workspace.id });
|
can(AbilityAction.Delete, 'Favorite', { workspaceId: workspace.id });
|
||||||
|
|
||||||
return build();
|
return build();
|
||||||
|
|||||||
@ -103,6 +103,7 @@ import {
|
|||||||
CreateFavoriteAbilityHandler,
|
CreateFavoriteAbilityHandler,
|
||||||
ReadFavoriteAbilityHandler,
|
ReadFavoriteAbilityHandler,
|
||||||
DeleteFavoriteAbilityHandler,
|
DeleteFavoriteAbilityHandler,
|
||||||
|
UpdateFavoriteAbilityHandler,
|
||||||
} from './handlers/favorite.ability-handler';
|
} from './handlers/favorite.ability-handler';
|
||||||
import {
|
import {
|
||||||
CreateViewSortAbilityHandler,
|
CreateViewSortAbilityHandler,
|
||||||
@ -219,6 +220,7 @@ import {
|
|||||||
//Favorite
|
//Favorite
|
||||||
ReadFavoriteAbilityHandler,
|
ReadFavoriteAbilityHandler,
|
||||||
CreateFavoriteAbilityHandler,
|
CreateFavoriteAbilityHandler,
|
||||||
|
UpdateFavoriteAbilityHandler,
|
||||||
DeleteFavoriteAbilityHandler,
|
DeleteFavoriteAbilityHandler,
|
||||||
// View
|
// View
|
||||||
ReadViewAbilityHandler,
|
ReadViewAbilityHandler,
|
||||||
|
|||||||
@ -57,6 +57,34 @@ export class CreateFavoriteAbilityHandler implements IAbilityHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UpdateFavoriteAbilityHandler implements IAbilityHandler {
|
||||||
|
constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
|
||||||
|
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||||
|
const gqlContext = GqlExecutionContext.create(context);
|
||||||
|
const args = gqlContext.getArgs<FavoriteArgs>();
|
||||||
|
|
||||||
|
const favorite = await this.prismaService.client.favorite.findFirst({
|
||||||
|
where: args.where,
|
||||||
|
});
|
||||||
|
assert(favorite, '', NotFoundException);
|
||||||
|
|
||||||
|
const allowed = await relationAbilityChecker(
|
||||||
|
'Favorite',
|
||||||
|
ability,
|
||||||
|
this.prismaService.client,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ability.can(AbilityAction.Update, 'Favorite');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteFavoriteAbilityHandler implements IAbilityHandler {
|
export class DeleteFavoriteAbilityHandler implements IAbilityHandler {
|
||||||
constructor(private readonly prismaService: PrismaService) {}
|
constructor(private readonly prismaService: PrismaService) {}
|
||||||
|
|||||||
@ -17,21 +17,28 @@ import {
|
|||||||
CreateFavoriteAbilityHandler,
|
CreateFavoriteAbilityHandler,
|
||||||
DeleteFavoriteAbilityHandler,
|
DeleteFavoriteAbilityHandler,
|
||||||
ReadFavoriteAbilityHandler,
|
ReadFavoriteAbilityHandler,
|
||||||
|
UpdateFavoriteAbilityHandler,
|
||||||
} from 'src/ability/handlers/favorite.ability-handler';
|
} from 'src/ability/handlers/favorite.ability-handler';
|
||||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||||
import { FavoriteService } from 'src/core/favorite/services/favorite.service';
|
import { FavoriteService } from 'src/core/favorite/services/favorite.service';
|
||||||
import { FavoriteWhereInput } from 'src/core/@generated/favorite/favorite-where.input';
|
import { FavoriteWhereInput } from 'src/core/@generated/favorite/favorite-where.input';
|
||||||
|
import { SortOrder } from 'src/core/@generated/prisma/sort-order.enum';
|
||||||
|
import { UpdateOneFavoriteArgs } from 'src/core/@generated/favorite/update-one-favorite.args';
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
class FavoriteMutationForPersonArgs {
|
class FavoriteMutationForPersonArgs {
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
personId: string;
|
personId: string;
|
||||||
|
@Field(() => Number)
|
||||||
|
position: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
class FavoriteMutationForCompanyArgs {
|
class FavoriteMutationForCompanyArgs {
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
companyId: string;
|
companyId: string;
|
||||||
|
@Field(() => Number)
|
||||||
|
position: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ -49,6 +56,7 @@ export class FavoriteResolver {
|
|||||||
where: {
|
where: {
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
},
|
},
|
||||||
|
orderBy: [{ position: SortOrder.asc }],
|
||||||
include: {
|
include: {
|
||||||
person: true,
|
person: true,
|
||||||
company: {
|
company: {
|
||||||
@ -86,6 +94,7 @@ export class FavoriteResolver {
|
|||||||
connect: { id: args.personId },
|
connect: { id: args.personId },
|
||||||
},
|
},
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
|
position: args.position,
|
||||||
},
|
},
|
||||||
select: prismaSelect.value,
|
select: prismaSelect.value,
|
||||||
});
|
});
|
||||||
@ -115,11 +124,29 @@ export class FavoriteResolver {
|
|||||||
connect: { id: args.companyId },
|
connect: { id: args.companyId },
|
||||||
},
|
},
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
|
position: args.position,
|
||||||
},
|
},
|
||||||
select: prismaSelect.value,
|
select: prismaSelect.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Favorite, {
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
@UseGuards(AbilityGuard)
|
||||||
|
@CheckAbilities(UpdateFavoriteAbilityHandler)
|
||||||
|
async updateOneFavorites(
|
||||||
|
@Args() args: UpdateOneFavoriteArgs,
|
||||||
|
@PrismaSelector({ modelName: 'Favorite' })
|
||||||
|
prismaSelect: PrismaSelect<'Favorite'>,
|
||||||
|
): Promise<Partial<Favorite>> {
|
||||||
|
return this.favoriteService.update({
|
||||||
|
data: args.data,
|
||||||
|
where: args.where,
|
||||||
|
select: prismaSelect.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => Favorite, {
|
@Mutation(() => Favorite, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `position` to the `favorites` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "favorites" ADD COLUMN "position" DOUBLE PRECISION NOT NULL;
|
||||||
@ -789,6 +789,7 @@ model Favorite {
|
|||||||
/// @TypeGraphQL.omit(input: true, output: false)
|
/// @TypeGraphQL.omit(input: true, output: false)
|
||||||
workspaceMember WorkspaceMember? @relation(fields: [workspaceMemberId], references: [id])
|
workspaceMember WorkspaceMember? @relation(fields: [workspaceMemberId], references: [id])
|
||||||
workspaceMemberId String?
|
workspaceMemberId String?
|
||||||
|
position Float
|
||||||
|
|
||||||
@@map("favorites")
|
@@map("favorites")
|
||||||
}
|
}
|
||||||
@ -892,19 +893,19 @@ model ViewField {
|
|||||||
model ApiKey {
|
model ApiKey {
|
||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
/// @Validator.IsOptional()
|
/// @Validator.IsOptional()
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
name String
|
name String
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
workspace Workspace @relation(fields: [workspaceId], references: [id])
|
workspace Workspace @relation(fields: [workspaceId], references: [id])
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
workspaceId String
|
workspaceId String
|
||||||
expiresAt DateTime?
|
expiresAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
revokedAt DateTime?
|
revokedAt DateTime?
|
||||||
|
|
||||||
@@map("api_keys")
|
@@map("api_keys")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user