mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-17 08:31:47 +03:00
parent
7a330b4a02
commit
4e654654da
@ -953,6 +953,7 @@ export type Mutation = {
|
||||
createEvent: Analytics;
|
||||
createFavoriteForCompany: Favorite;
|
||||
createFavoriteForPerson: Favorite;
|
||||
createManyView: AffectedRows;
|
||||
createManyViewField: AffectedRows;
|
||||
createManyViewSort: AffectedRows;
|
||||
createOneActivity: Activity;
|
||||
@ -978,6 +979,7 @@ export type Mutation = {
|
||||
updateOnePerson?: Maybe<Person>;
|
||||
updateOnePipelineProgress?: Maybe<PipelineProgress>;
|
||||
updateOnePipelineStage?: Maybe<PipelineStage>;
|
||||
updateOneView: View;
|
||||
updateOneViewField: ViewField;
|
||||
updateOneViewSort: ViewSort;
|
||||
updateUser: User;
|
||||
@ -1019,6 +1021,12 @@ export type MutationCreateFavoriteForPersonArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateManyViewArgs = {
|
||||
data: Array<ViewCreateManyInput>;
|
||||
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateManyViewFieldArgs = {
|
||||
data: Array<ViewFieldCreateManyInput>;
|
||||
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||
@ -1143,6 +1151,12 @@ export type MutationUpdateOnePipelineStageArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateOneViewArgs = {
|
||||
data: ViewUpdateInput;
|
||||
where: ViewWhereUniqueInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateOneViewFieldArgs = {
|
||||
data: ViewFieldUpdateInput;
|
||||
where: ViewFieldWhereUniqueInput;
|
||||
@ -1866,6 +1880,7 @@ export type Query = {
|
||||
findManyPipelineProgress: Array<PipelineProgress>;
|
||||
findManyPipelineStage: Array<PipelineStage>;
|
||||
findManyUser: Array<User>;
|
||||
findManyView: Array<View>;
|
||||
findManyViewField: Array<ViewField>;
|
||||
findManyViewSort: Array<ViewSort>;
|
||||
findManyWorkspaceMember: Array<WorkspaceMember>;
|
||||
@ -1955,6 +1970,16 @@ export type QueryFindManyUserArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindManyViewArgs = {
|
||||
cursor?: InputMaybe<ViewWhereUniqueInput>;
|
||||
distinct?: InputMaybe<Array<ViewScalarFieldEnum>>;
|
||||
orderBy?: InputMaybe<Array<ViewOrderByWithRelationInput>>;
|
||||
skip?: InputMaybe<Scalars['Int']>;
|
||||
take?: InputMaybe<Scalars['Int']>;
|
||||
where?: InputMaybe<ViewWhereInput>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindManyViewFieldArgs = {
|
||||
cursor?: InputMaybe<ViewFieldWhereUniqueInput>;
|
||||
distinct?: InputMaybe<Array<ViewFieldScalarFieldEnum>>;
|
||||
@ -2283,6 +2308,13 @@ export type View = {
|
||||
type: ViewType;
|
||||
};
|
||||
|
||||
export type ViewCreateManyInput = {
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
name: Scalars['String'];
|
||||
objectId: Scalars['String'];
|
||||
type: ViewType;
|
||||
};
|
||||
|
||||
export type ViewCreateNestedOneWithoutFieldsInput = {
|
||||
connect?: InputMaybe<ViewWhereUniqueInput>;
|
||||
};
|
||||
@ -2361,6 +2393,12 @@ export type ViewFieldUpdateInput = {
|
||||
view?: InputMaybe<ViewUpdateOneWithoutFieldsNestedInput>;
|
||||
};
|
||||
|
||||
export type ViewFieldUpdateManyWithoutViewNestedInput = {
|
||||
connect?: InputMaybe<Array<ViewFieldWhereUniqueInput>>;
|
||||
disconnect?: InputMaybe<Array<ViewFieldWhereUniqueInput>>;
|
||||
set?: InputMaybe<Array<ViewFieldWhereUniqueInput>>;
|
||||
};
|
||||
|
||||
export type ViewFieldUpdateManyWithoutWorkspaceNestedInput = {
|
||||
connect?: InputMaybe<Array<ViewFieldWhereUniqueInput>>;
|
||||
disconnect?: InputMaybe<Array<ViewFieldWhereUniqueInput>>;
|
||||
@ -2406,6 +2444,14 @@ export type ViewRelationFilter = {
|
||||
isNot?: InputMaybe<ViewWhereInput>;
|
||||
};
|
||||
|
||||
export enum ViewScalarFieldEnum {
|
||||
Id = 'id',
|
||||
Name = 'name',
|
||||
ObjectId = 'objectId',
|
||||
Type = 'type',
|
||||
WorkspaceId = 'workspaceId'
|
||||
}
|
||||
|
||||
export type ViewSort = {
|
||||
__typename?: 'ViewSort';
|
||||
direction: ViewSortDirection;
|
||||
@ -2460,6 +2506,12 @@ export type ViewSortUpdateInput = {
|
||||
view?: InputMaybe<ViewUpdateOneRequiredWithoutSortsNestedInput>;
|
||||
};
|
||||
|
||||
export type ViewSortUpdateManyWithoutViewNestedInput = {
|
||||
connect?: InputMaybe<Array<ViewSortWhereUniqueInput>>;
|
||||
disconnect?: InputMaybe<Array<ViewSortWhereUniqueInput>>;
|
||||
set?: InputMaybe<Array<ViewSortWhereUniqueInput>>;
|
||||
};
|
||||
|
||||
export type ViewSortUpdateManyWithoutWorkspaceNestedInput = {
|
||||
connect?: InputMaybe<Array<ViewSortWhereUniqueInput>>;
|
||||
disconnect?: InputMaybe<Array<ViewSortWhereUniqueInput>>;
|
||||
@ -2491,6 +2543,15 @@ export enum ViewType {
|
||||
Table = 'Table'
|
||||
}
|
||||
|
||||
export type ViewUpdateInput = {
|
||||
fields?: InputMaybe<ViewFieldUpdateManyWithoutViewNestedInput>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
objectId?: InputMaybe<Scalars['String']>;
|
||||
sorts?: InputMaybe<ViewSortUpdateManyWithoutViewNestedInput>;
|
||||
type?: InputMaybe<ViewType>;
|
||||
};
|
||||
|
||||
export type ViewUpdateManyWithoutWorkspaceNestedInput = {
|
||||
connect?: InputMaybe<Array<ViewWhereUniqueInput>>;
|
||||
disconnect?: InputMaybe<Array<ViewWhereUniqueInput>>;
|
||||
@ -3086,6 +3147,13 @@ export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type GetUsersQuery = { __typename?: 'Query', findManyUser: Array<{ __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null }> };
|
||||
|
||||
export type CreateViewsMutationVariables = Exact<{
|
||||
data: Array<ViewCreateManyInput> | ViewCreateManyInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateViewsMutation = { __typename?: 'Mutation', createManyView: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type CreateViewFieldsMutationVariables = Exact<{
|
||||
data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput;
|
||||
}>;
|
||||
@ -3107,6 +3175,14 @@ export type DeleteViewSortsMutationVariables = Exact<{
|
||||
|
||||
export type DeleteViewSortsMutation = { __typename?: 'Mutation', deleteManyViewSort: { __typename?: 'AffectedRows', count: number } };
|
||||
|
||||
export type UpdateViewMutationVariables = Exact<{
|
||||
data: ViewUpdateInput;
|
||||
where: ViewWhereUniqueInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateViewMutation = { __typename?: 'Mutation', updateOneView: { __typename?: 'View', id: string, name: string } };
|
||||
|
||||
export type UpdateViewFieldMutationVariables = Exact<{
|
||||
data: ViewFieldUpdateInput;
|
||||
where: ViewFieldWhereUniqueInput;
|
||||
@ -3123,6 +3199,13 @@ export type UpdateViewSortMutationVariables = Exact<{
|
||||
|
||||
export type UpdateViewSortMutation = { __typename?: 'Mutation', viewSort: { __typename?: 'ViewSort', direction: ViewSortDirection, key: string, name: string } };
|
||||
|
||||
export type GetViewsQueryVariables = Exact<{
|
||||
where?: InputMaybe<ViewWhereInput>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetViewsQuery = { __typename?: 'Query', views: Array<{ __typename?: 'View', id: string, name: string }> };
|
||||
|
||||
export type GetViewFieldsQueryVariables = Exact<{
|
||||
where?: InputMaybe<ViewFieldWhereInput>;
|
||||
orderBy?: InputMaybe<Array<ViewFieldOrderByWithRelationInput> | ViewFieldOrderByWithRelationInput>;
|
||||
@ -5680,6 +5763,39 @@ export function useGetUsersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<G
|
||||
export type GetUsersQueryHookResult = ReturnType<typeof useGetUsersQuery>;
|
||||
export type GetUsersLazyQueryHookResult = ReturnType<typeof useGetUsersLazyQuery>;
|
||||
export type GetUsersQueryResult = Apollo.QueryResult<GetUsersQuery, GetUsersQueryVariables>;
|
||||
export const CreateViewsDocument = gql`
|
||||
mutation CreateViews($data: [ViewCreateManyInput!]!) {
|
||||
createManyView(data: $data) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type CreateViewsMutationFn = Apollo.MutationFunction<CreateViewsMutation, CreateViewsMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateViewsMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateViewsMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateViewsMutation` 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 [createViewsMutation, { data, loading, error }] = useCreateViewsMutation({
|
||||
* variables: {
|
||||
* data: // value for 'data'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateViewsMutation(baseOptions?: Apollo.MutationHookOptions<CreateViewsMutation, CreateViewsMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateViewsMutation, CreateViewsMutationVariables>(CreateViewsDocument, options);
|
||||
}
|
||||
export type CreateViewsMutationHookResult = ReturnType<typeof useCreateViewsMutation>;
|
||||
export type CreateViewsMutationResult = Apollo.MutationResult<CreateViewsMutation>;
|
||||
export type CreateViewsMutationOptions = Apollo.BaseMutationOptions<CreateViewsMutation, CreateViewsMutationVariables>;
|
||||
export const CreateViewFieldsDocument = gql`
|
||||
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
|
||||
createManyViewField(data: $data) {
|
||||
@ -5779,6 +5895,41 @@ export function useDeleteViewSortsMutation(baseOptions?: Apollo.MutationHookOpti
|
||||
export type DeleteViewSortsMutationHookResult = ReturnType<typeof useDeleteViewSortsMutation>;
|
||||
export type DeleteViewSortsMutationResult = Apollo.MutationResult<DeleteViewSortsMutation>;
|
||||
export type DeleteViewSortsMutationOptions = Apollo.BaseMutationOptions<DeleteViewSortsMutation, DeleteViewSortsMutationVariables>;
|
||||
export const UpdateViewDocument = gql`
|
||||
mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) {
|
||||
updateOneView(data: $data, where: $where) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateViewMutationFn = Apollo.MutationFunction<UpdateViewMutation, UpdateViewMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateViewMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateViewMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateViewMutation` 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 [updateViewMutation, { data, loading, error }] = useUpdateViewMutation({
|
||||
* variables: {
|
||||
* data: // value for 'data'
|
||||
* where: // value for 'where'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateViewMutation(baseOptions?: Apollo.MutationHookOptions<UpdateViewMutation, UpdateViewMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateViewMutation, UpdateViewMutationVariables>(UpdateViewDocument, options);
|
||||
}
|
||||
export type UpdateViewMutationHookResult = ReturnType<typeof useUpdateViewMutation>;
|
||||
export type UpdateViewMutationResult = Apollo.MutationResult<UpdateViewMutation>;
|
||||
export type UpdateViewMutationOptions = Apollo.BaseMutationOptions<UpdateViewMutation, UpdateViewMutationVariables>;
|
||||
export const UpdateViewFieldDocument = gql`
|
||||
mutation UpdateViewField($data: ViewFieldUpdateInput!, $where: ViewFieldWhereUniqueInput!) {
|
||||
updateOneViewField(data: $data, where: $where) {
|
||||
@ -5853,6 +6004,42 @@ export function useUpdateViewSortMutation(baseOptions?: Apollo.MutationHookOptio
|
||||
export type UpdateViewSortMutationHookResult = ReturnType<typeof useUpdateViewSortMutation>;
|
||||
export type UpdateViewSortMutationResult = Apollo.MutationResult<UpdateViewSortMutation>;
|
||||
export type UpdateViewSortMutationOptions = Apollo.BaseMutationOptions<UpdateViewSortMutation, UpdateViewSortMutationVariables>;
|
||||
export const GetViewsDocument = gql`
|
||||
query GetViews($where: ViewWhereInput) {
|
||||
views: findManyView(where: $where) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetViewsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetViewsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetViewsQuery` 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 } = useGetViewsQuery({
|
||||
* variables: {
|
||||
* where: // value for 'where'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetViewsQuery(baseOptions?: Apollo.QueryHookOptions<GetViewsQuery, GetViewsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetViewsQuery, GetViewsQueryVariables>(GetViewsDocument, options);
|
||||
}
|
||||
export function useGetViewsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetViewsQuery, GetViewsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetViewsQuery, GetViewsQueryVariables>(GetViewsDocument, options);
|
||||
}
|
||||
export type GetViewsQueryHookResult = ReturnType<typeof useGetViewsQuery>;
|
||||
export type GetViewsLazyQueryHookResult = ReturnType<typeof useGetViewsLazyQuery>;
|
||||
export type GetViewsQueryResult = Apollo.QueryResult<GetViewsQuery, GetViewsQueryVariables>;
|
||||
export const GetViewFieldsDocument = gql`
|
||||
query GetViewFields($where: ViewFieldWhereInput, $orderBy: [ViewFieldOrderByWithRelationInput!]) {
|
||||
viewFields: findManyViewField(where: $where, orderBy: $orderBy) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { companyViewFields } from '@/companies/constants/companyViewFields';
|
||||
import { useCompanyTableActionBarEntries } from '@/companies/hooks/useCompanyTableActionBarEntries';
|
||||
@ -7,15 +6,15 @@ import { useCompanyTableContextMenuEntries } from '@/companies/hooks/useCompanyT
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { sortsOrderByScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
|
||||
import { IconList } from '@/ui/icon';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useTableViews } from '@/views/hooks/useTableViews';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import {
|
||||
SortOrder,
|
||||
UpdateOneCompanyMutationVariables,
|
||||
@ -26,7 +25,10 @@ import { companiesFilters } from '~/pages/companies/companies-filters';
|
||||
import { availableSorts } from '~/pages/companies/companies-sorts';
|
||||
|
||||
export function CompanyTable() {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const orderBy = useRecoilScopedValue(
|
||||
sortsOrderByScopedState,
|
||||
TableRecoilScopeContext,
|
||||
@ -34,14 +36,13 @@ export function CompanyTable() {
|
||||
const [updateEntityMutation] = useUpdateOneCompanyMutation();
|
||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||
|
||||
const objectId = 'company';
|
||||
const { handleViewsChange } = useTableViews({ objectId });
|
||||
const { handleColumnsChange } = useTableViewFields({
|
||||
objectName: 'company',
|
||||
objectName: objectId,
|
||||
viewFieldDefinitions: companyViewFields,
|
||||
});
|
||||
const { updateSorts } = useViewSorts({
|
||||
availableSorts,
|
||||
Context: TableRecoilScopeContext,
|
||||
});
|
||||
const { updateSorts } = useViewSorts({ availableSorts });
|
||||
|
||||
const filters = useRecoilScopedValue(
|
||||
filtersScopedState,
|
||||
@ -76,10 +77,10 @@ export function CompanyTable() {
|
||||
/>
|
||||
<EntityTable
|
||||
viewName="All Companies"
|
||||
viewIcon={<IconList size={16} />}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onSortsUpdate={currentViewId ? updateSorts : undefined}
|
||||
onViewsChange={handleViewsChange}
|
||||
updateEntityMutation={({
|
||||
variables,
|
||||
}: {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { IconList } from '@/ui/icon';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { useUpdateOneCompanyMutation } from '~/generated/graphql';
|
||||
import { availableSorts } from '~/pages/companies/companies-sorts';
|
||||
@ -11,7 +10,6 @@ export function CompanyTableMockMode() {
|
||||
<CompanyTableMockData />
|
||||
<EntityTable
|
||||
viewName="All Companies"
|
||||
viewIcon={<IconList size={16} />}
|
||||
availableSorts={availableSorts}
|
||||
updateEntityMutation={[useUpdateOneCompanyMutation()]}
|
||||
/>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { peopleViewFields } from '@/people/constants/peopleViewFields';
|
||||
import { usePersonTableContextMenuEntries } from '@/people/hooks/usePeopleTableContextMenuEntries';
|
||||
@ -7,15 +6,15 @@ import { usePersonTableActionBarEntries } from '@/people/hooks/usePersonTableAct
|
||||
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
|
||||
import { sortsOrderByScopedState } from '@/ui/filter-n-sort/states/sortScopedState';
|
||||
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
|
||||
import { IconList } from '@/ui/icon';
|
||||
import { EntityTable } from '@/ui/table/components/EntityTable';
|
||||
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
|
||||
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
|
||||
import { useTableViews } from '@/views/hooks/useTableViews';
|
||||
import { useViewSorts } from '@/views/hooks/useViewSorts';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import {
|
||||
SortOrder,
|
||||
UpdateOnePersonMutationVariables,
|
||||
@ -26,7 +25,10 @@ import { peopleFilters } from '~/pages/people/people-filters';
|
||||
import { availableSorts } from '~/pages/people/people-sorts';
|
||||
|
||||
export function PeopleTable() {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const orderBy = useRecoilScopedValue(
|
||||
sortsOrderByScopedState,
|
||||
TableRecoilScopeContext,
|
||||
@ -34,14 +36,13 @@ export function PeopleTable() {
|
||||
const [updateEntityMutation] = useUpdateOnePersonMutation();
|
||||
const upsertEntityTableItem = useUpsertEntityTableItem();
|
||||
|
||||
const objectId = 'person';
|
||||
const { handleViewsChange } = useTableViews({ objectId });
|
||||
const { handleColumnsChange } = useTableViewFields({
|
||||
objectName: 'person',
|
||||
objectName: objectId,
|
||||
viewFieldDefinitions: peopleViewFields,
|
||||
});
|
||||
const { updateSorts } = useViewSorts({
|
||||
availableSorts,
|
||||
Context: TableRecoilScopeContext,
|
||||
});
|
||||
const { updateSorts } = useViewSorts({ availableSorts });
|
||||
|
||||
const filters = useRecoilScopedValue(
|
||||
filtersScopedState,
|
||||
@ -76,10 +77,10 @@ export function PeopleTable() {
|
||||
/>
|
||||
<EntityTable
|
||||
viewName="All People"
|
||||
viewIcon={<IconList size={16} />}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={handleColumnsChange}
|
||||
onSortsUpdate={currentViewId ? updateSorts : undefined}
|
||||
onViewsChange={handleViewsChange}
|
||||
updateEntityMutation={({
|
||||
variables,
|
||||
}: {
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { forwardRef, InputHTMLAttributes } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { textInputStyle } from '@/ui/theme/constants/effects';
|
||||
|
||||
export const DropdownMenuInputContainer = styled.div`
|
||||
--vertical-padding: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(36px - 2 * var(--vertical-padding));
|
||||
padding: var(--vertical-padding) 0;
|
||||
|
||||
width: calc(100%);
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
|
||||
${textInputStyle}
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const DropdownMenuInput = forwardRef<
|
||||
HTMLInputElement,
|
||||
InputHTMLAttributes<HTMLInputElement>
|
||||
>((props, ref) => (
|
||||
<DropdownMenuInputContainer>
|
||||
<StyledInput autoComplete="off" placeholder="Search" {...props} ref={ref} />
|
||||
</DropdownMenuInputContainer>
|
||||
));
|
@ -1,39 +0,0 @@
|
||||
import { InputHTMLAttributes } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { textInputStyle } from '@/ui/theme/constants/effects';
|
||||
|
||||
export const DropdownMenuSearchContainer = styled.div`
|
||||
--vertical-padding: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(36px - 2 * var(--vertical-padding));
|
||||
padding: var(--vertical-padding) 0;
|
||||
|
||||
width: calc(100%);
|
||||
`;
|
||||
|
||||
const StyledEditModeSearchInput = styled.input`
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
|
||||
${textInputStyle}
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export function DropdownMenuSearch(
|
||||
props: InputHTMLAttributes<HTMLInputElement>,
|
||||
) {
|
||||
return (
|
||||
<DropdownMenuSearchContainer>
|
||||
<StyledEditModeSearchInput
|
||||
autoComplete="off"
|
||||
{...props}
|
||||
placeholder={props.placeholder ?? 'Search'}
|
||||
/>
|
||||
</DropdownMenuSearchContainer>
|
||||
);
|
||||
}
|
@ -11,9 +11,9 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { DropdownMenu } from '../DropdownMenu';
|
||||
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
|
||||
import { DropdownMenuHeader } from '../DropdownMenuHeader';
|
||||
import { DropdownMenuInput } from '../DropdownMenuInput';
|
||||
import { DropdownMenuItem } from '../DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearch } from '../DropdownMenuSearch';
|
||||
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
|
||||
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
|
||||
import { DropdownMenuSubheader } from '../DropdownMenuSubheader';
|
||||
@ -256,7 +256,7 @@ export const LoadingMenu: Story = {
|
||||
...WithContentBelow,
|
||||
render: () => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuSearch value={'query'} autoFocus />
|
||||
<DropdownMenuInput value={'query'} autoFocus />
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
<DropdownMenuSkeletonItem />
|
||||
@ -269,7 +269,7 @@ export const Search: Story = {
|
||||
...WithContentBelow,
|
||||
render: (args) => (
|
||||
<DropdownMenu {...args}>
|
||||
<DropdownMenuSearch />
|
||||
<DropdownMenuInput />
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer hasMaxHeight>
|
||||
{mockSelectArray.map(({ name }) => (
|
||||
|
@ -4,19 +4,18 @@ import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
||||
|
||||
import { DropdownMenuContainer } from './DropdownMenuContainer';
|
||||
|
||||
type OwnProps = {
|
||||
label: string;
|
||||
anchor?: 'left' | 'right';
|
||||
label: ReactNode;
|
||||
isActive: boolean;
|
||||
children?: ReactNode;
|
||||
isUnfolded?: boolean;
|
||||
icon?: ReactNode;
|
||||
onIsUnfoldedChange?: (newIsUnfolded: boolean) => void;
|
||||
resetState?: () => void;
|
||||
HotkeyScope: FiltersHotkeyScope;
|
||||
HotkeyScope: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
@ -59,7 +58,12 @@ const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDropdownMenuContainer = styled(DropdownMenuContainer)`
|
||||
z-index: 2;
|
||||
`;
|
||||
|
||||
function DropdownButton({
|
||||
anchor,
|
||||
label,
|
||||
isActive,
|
||||
children,
|
||||
@ -99,9 +103,9 @@ function DropdownButton({
|
||||
{label}
|
||||
</StyledDropdownButton>
|
||||
{isUnfolded && (
|
||||
<DropdownMenuContainer onClose={onOutsideClick}>
|
||||
<StyledDropdownMenuContainer anchor={anchor} onClose={onOutsideClick}>
|
||||
{children}
|
||||
</DropdownMenuContainer>
|
||||
</StyledDropdownMenuContainer>
|
||||
)}
|
||||
</StyledDropdownButtonContainer>
|
||||
);
|
||||
|
@ -1,23 +1,32 @@
|
||||
import { useRef } from 'react';
|
||||
import { type HTMLAttributes, useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
|
||||
import { DropdownMenu } from '../../dropdown/components/DropdownMenu';
|
||||
|
||||
export const StyledDropdownMenuContainer = styled.ul`
|
||||
export const StyledDropdownMenuContainer = styled.ul<{
|
||||
anchor: 'left' | 'right';
|
||||
}>`
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
${({ anchor }) => {
|
||||
if (anchor === 'right') return 'right: 0';
|
||||
}};
|
||||
top: 14px;
|
||||
`;
|
||||
|
||||
export function DropdownMenuContainer({
|
||||
children,
|
||||
onClose,
|
||||
}: {
|
||||
type DropdownMenuContainerProps = {
|
||||
anchor?: 'left' | 'right';
|
||||
children: React.ReactNode;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
} & HTMLAttributes<HTMLUListElement>;
|
||||
|
||||
export function DropdownMenuContainer({
|
||||
anchor = 'right',
|
||||
children,
|
||||
onClose,
|
||||
...props
|
||||
}: DropdownMenuContainerProps) {
|
||||
const dropdownRef = useRef(null);
|
||||
|
||||
useListenClickOutside({
|
||||
@ -28,7 +37,7 @@ export function DropdownMenuContainer({
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledDropdownMenuContainer data-select-disable>
|
||||
<StyledDropdownMenuContainer data-select-disable {...props} anchor={anchor}>
|
||||
<DropdownMenu ref={dropdownRef}>{children}</DropdownMenu>
|
||||
</StyledDropdownMenuContainer>
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ChangeEvent, Context } from 'react';
|
||||
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||
import { filterDefinitionUsedInDropdownScopedState } from '@/ui/filter-n-sort/states/filterDefinitionUsedInDropdownScopedState';
|
||||
import { filterDropdownSearchInputScopedState } from '@/ui/filter-n-sort/states/filterDropdownSearchInputScopedState';
|
||||
import { selectedOperandInDropdownScopedState } from '@/ui/filter-n-sort/states/selectedOperandInDropdownScopedState';
|
||||
@ -27,7 +27,7 @@ export function FilterDropdownEntitySearchInput({
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuSearch
|
||||
<DropdownMenuInput
|
||||
type="text"
|
||||
value={filterDropdownSearchInput}
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ChangeEvent, Context } from 'react';
|
||||
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { useRemoveFilter } from '../hooks/useRemoveFilter';
|
||||
@ -29,7 +29,7 @@ export function FilterDropdownNumberSearchInput({
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuSearch
|
||||
<DropdownMenuInput
|
||||
type="number"
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ChangeEvent, Context } from 'react';
|
||||
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { useFilterCurrentlyEdited } from '../hooks/useFilterCurrentlyEdited';
|
||||
@ -36,7 +36,7 @@ export function FilterDropdownTextSearchInput({
|
||||
return (
|
||||
filterDefinitionUsedInDropdown &&
|
||||
selectedOperandInDropdown && (
|
||||
<DropdownMenuSearch
|
||||
<DropdownMenuInput
|
||||
type="text"
|
||||
placeholder={filterDefinitionUsedInDropdown.label}
|
||||
value={filterCurrentlyEdited?.value ?? filterDropdownSearchInput}
|
||||
|
@ -1,58 +1,60 @@
|
||||
export { IconAddressBook } from './components/IconAddressBook';
|
||||
export { IconBuildingSkyscraper } from '@tabler/icons-react';
|
||||
export { IconMessageCircle as IconComment } from '@tabler/icons-react';
|
||||
export { IconCheck } from '@tabler/icons-react';
|
||||
export { IconTrash } from '@tabler/icons-react';
|
||||
export { IconLayoutSidebarRightCollapse } from '@tabler/icons-react';
|
||||
export { IconLayoutSidebarLeftCollapse } from '@tabler/icons-react';
|
||||
export { IconUser } from '@tabler/icons-react';
|
||||
export { IconBell } from '@tabler/icons-react';
|
||||
export { IconList } from '@tabler/icons-react';
|
||||
export { IconInbox } from '@tabler/icons-react';
|
||||
export { IconSearch } from '@tabler/icons-react';
|
||||
export { IconArchive } from '@tabler/icons-react';
|
||||
export { IconSettings } from '@tabler/icons-react';
|
||||
export { IconLogout } from '@tabler/icons-react';
|
||||
export { IconColorSwatch } from '@tabler/icons-react';
|
||||
export { IconProgressCheck } from '@tabler/icons-react';
|
||||
export { IconX } from '@tabler/icons-react';
|
||||
export { IconChevronLeft } from '@tabler/icons-react';
|
||||
export { IconBriefcase } from '@tabler/icons-react';
|
||||
export { IconPlus } from '@tabler/icons-react';
|
||||
export { IconMinus } from '@tabler/icons-react';
|
||||
export { IconLink } from '@tabler/icons-react';
|
||||
export { IconBrandLinkedin } from '@tabler/icons-react';
|
||||
export { IconUsers } from '@tabler/icons-react';
|
||||
export { IconCalendarEvent } from '@tabler/icons-react';
|
||||
export { IconMap } from '@tabler/icons-react';
|
||||
export { IconMail } from '@tabler/icons-react';
|
||||
export { IconPhone } from '@tabler/icons-react';
|
||||
export { IconTargetArrow } from '@tabler/icons-react';
|
||||
export { IconChevronDown } from '@tabler/icons-react';
|
||||
export { IconArrowNarrowDown } from '@tabler/icons-react';
|
||||
export { IconArrowNarrowUp } from '@tabler/icons-react';
|
||||
export { IconArrowRight } from '@tabler/icons-react';
|
||||
export { IconArrowUpRight } from '@tabler/icons-react';
|
||||
export { IconBrandGoogle } from '@tabler/icons-react';
|
||||
export { IconUpload } from '@tabler/icons-react';
|
||||
export { IconFileUpload } from '@tabler/icons-react';
|
||||
export { IconChevronsRight } from '@tabler/icons-react';
|
||||
export { IconNotes } from '@tabler/icons-react';
|
||||
export { IconCirclePlus } from '@tabler/icons-react';
|
||||
export { IconCheckbox } from '@tabler/icons-react';
|
||||
export { IconTimelineEvent } from '@tabler/icons-react';
|
||||
export { IconAlertCircle } from '@tabler/icons-react';
|
||||
export { IconEye } from '@tabler/icons-react';
|
||||
export { IconEyeOff } from '@tabler/icons-react';
|
||||
export { IconAlertTriangle } from '@tabler/icons-react';
|
||||
export { IconCopy } from '@tabler/icons-react';
|
||||
export { IconCurrencyDollar } from '@tabler/icons-react';
|
||||
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';
|
||||
export { IconBrandGithub } from '@tabler/icons-react';
|
||||
export {
|
||||
IconAlertCircle,
|
||||
IconAlertTriangle,
|
||||
IconArchive,
|
||||
IconArrowNarrowDown,
|
||||
IconArrowNarrowUp,
|
||||
IconArrowRight,
|
||||
IconArrowUpRight,
|
||||
IconBell,
|
||||
IconBrandGithub,
|
||||
IconBrandGoogle,
|
||||
IconBrandLinkedin,
|
||||
IconBrandX,
|
||||
IconBriefcase,
|
||||
IconBuildingSkyscraper,
|
||||
IconCalendar,
|
||||
IconCalendarEvent,
|
||||
IconCheck,
|
||||
IconCheckbox,
|
||||
IconChevronDown,
|
||||
IconChevronLeft,
|
||||
IconChevronsRight,
|
||||
IconCircleDot,
|
||||
IconCirclePlus,
|
||||
IconColorSwatch,
|
||||
IconMessageCircle as IconComment,
|
||||
IconCopy,
|
||||
IconCurrencyDollar,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
IconFileUpload,
|
||||
IconHeart,
|
||||
IconHelpCircle,
|
||||
IconInbox,
|
||||
IconLayoutSidebarLeftCollapse,
|
||||
IconLayoutSidebarRightCollapse,
|
||||
IconLink,
|
||||
IconList,
|
||||
IconLogout,
|
||||
IconMail,
|
||||
IconMap,
|
||||
IconMinus,
|
||||
IconNotes,
|
||||
IconPencil,
|
||||
IconPhone,
|
||||
IconPlus,
|
||||
IconProgressCheck,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconTag,
|
||||
IconTargetArrow,
|
||||
IconTimelineEvent,
|
||||
IconTrash,
|
||||
IconUpload,
|
||||
IconUser,
|
||||
IconUserCircle,
|
||||
IconUsers,
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
|
@ -3,9 +3,9 @@ import debounce from 'lodash.debounce';
|
||||
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuCheckableItem } from '@/ui/dropdown/components/DropdownMenuCheckableItem';
|
||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
@ -73,7 +73,7 @@ export function MultipleEntitySelect<
|
||||
|
||||
return (
|
||||
<DropdownMenu ref={containerRef}>
|
||||
<DropdownMenuSearch
|
||||
<DropdownMenuInput
|
||||
value={searchFilter}
|
||||
onChange={handleFilterChange}
|
||||
autoFocus
|
||||
|
@ -2,9 +2,9 @@ import { useRef } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSearch } from '@/ui/dropdown/components/DropdownMenuSearch';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import { IconPlus } from '@/ui/icon';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
@ -65,7 +65,7 @@ export function SingleEntitySelect<
|
||||
ref={containerRef}
|
||||
width={width}
|
||||
>
|
||||
<DropdownMenuSearch
|
||||
<DropdownMenuInput
|
||||
value={searchFilter}
|
||||
onChange={handleSearchFilterChange}
|
||||
autoFocus
|
||||
|
@ -14,6 +14,7 @@ import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
||||
import { useResetTableRowSelection } from '../hooks/useResetTableRowSelection';
|
||||
import { useSetRowSelectedState } from '../hooks/useSetRowSelectedState';
|
||||
import type { TableView } from '../states/tableViewsState';
|
||||
import { TableHeader } from '../table-header/components/TableHeader';
|
||||
|
||||
import { EntityTableBody } from './EntityTableBody';
|
||||
@ -97,16 +98,16 @@ type OwnProps<SortField> = {
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
onRowSelectionChange?: (rowSelection: string[]) => void;
|
||||
onViewsChange?: (views: TableView[]) => void;
|
||||
updateEntityMutation: any;
|
||||
};
|
||||
|
||||
export function EntityTable<SortField>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onSortsUpdate,
|
||||
onViewsChange,
|
||||
updateEntityMutation,
|
||||
}: OwnProps<SortField>) {
|
||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||
@ -131,10 +132,10 @@ export function EntityTable<SortField>({
|
||||
<StyledTableContainer ref={tableBodyRef}>
|
||||
<TableHeader
|
||||
viewName={viewName}
|
||||
viewIcon={viewIcon}
|
||||
availableSorts={availableSorts}
|
||||
onColumnsChange={onColumnsChange}
|
||||
onSortsUpdate={onSortsUpdate}
|
||||
onViewsChange={onViewsChange}
|
||||
/>
|
||||
<StyledTableWrapper>
|
||||
<StyledTable>
|
||||
|
@ -1,30 +1,50 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
type FormEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { IconButton } from '@/ui/button/components/IconButton';
|
||||
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
|
||||
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
|
||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import {
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
||||
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
||||
import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon';
|
||||
import {
|
||||
hiddenTableColumnsState,
|
||||
tableColumnsState,
|
||||
visibleTableColumnsState,
|
||||
} from '@/ui/table/states/tableColumnsState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import {
|
||||
type TableView,
|
||||
tableViewEditModeState,
|
||||
tableViewsByIdState,
|
||||
tableViewsState,
|
||||
} from '../../states/tableViewsState';
|
||||
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
||||
|
||||
import { TableOptionsDropdownSection } from './TableOptionsDropdownSection';
|
||||
|
||||
type TableOptionsDropdownButtonProps = {
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
HotkeyScope: FiltersHotkeyScope;
|
||||
onViewsChange?: (views: TableView[]) => void;
|
||||
HotkeyScope: TableOptionsHotkeyScope;
|
||||
};
|
||||
|
||||
enum Option {
|
||||
@ -33,17 +53,37 @@ enum Option {
|
||||
|
||||
export const TableOptionsDropdownButton = ({
|
||||
onColumnsChange,
|
||||
onViewsChange,
|
||||
HotkeyScope,
|
||||
}: TableOptionsDropdownButtonProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const viewEditInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [columns, setColumns] = useRecoilState(tableColumnsState);
|
||||
const [viewEditMode, setViewEditMode] = useRecoilState(
|
||||
tableViewEditModeState,
|
||||
);
|
||||
const [views, setViews] = useRecoilScopedState(
|
||||
tableViewsState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const visibleColumns = useRecoilValue(visibleTableColumnsState);
|
||||
const hiddenColumns = useRecoilValue(hiddenTableColumnsState);
|
||||
const viewsById = useRecoilScopedValue(
|
||||
tableViewsByIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleColumnVisibilityChange = useCallback(
|
||||
(columnId: string, nextIsVisible: boolean) => {
|
||||
@ -79,25 +119,109 @@ export const TableOptionsDropdownButton = ({
|
||||
[handleColumnVisibilityChange, theme.icon.size.sm, visibleColumns.length],
|
||||
);
|
||||
|
||||
const resetViewEditMode = useCallback(() => {
|
||||
setViewEditMode({ mode: undefined, viewId: undefined });
|
||||
|
||||
if (viewEditInputRef.current) {
|
||||
viewEditInputRef.current.value = '';
|
||||
}
|
||||
}, [setViewEditMode]);
|
||||
|
||||
const handleViewNameSubmit = useCallback(
|
||||
(event?: FormEvent) => {
|
||||
event?.preventDefault();
|
||||
|
||||
if (viewEditMode.mode && viewEditInputRef.current?.value) {
|
||||
const name = viewEditInputRef.current.value;
|
||||
const nextViews =
|
||||
viewEditMode.mode === 'create'
|
||||
? [...views, { id: v4(), name }]
|
||||
: views.map((view) =>
|
||||
view.id === viewEditMode.viewId ? { ...view, name } : view,
|
||||
);
|
||||
|
||||
(onViewsChange ?? setViews)(nextViews);
|
||||
}
|
||||
|
||||
resetViewEditMode();
|
||||
},
|
||||
[
|
||||
onViewsChange,
|
||||
resetViewEditMode,
|
||||
setViews,
|
||||
viewEditMode.mode,
|
||||
viewEditMode.viewId,
|
||||
views,
|
||||
],
|
||||
);
|
||||
|
||||
const handleSelectOption = useCallback(
|
||||
(option: Option) => {
|
||||
handleViewNameSubmit();
|
||||
setIsUnfolded(true);
|
||||
setSelectedOption(option);
|
||||
},
|
||||
[handleViewNameSubmit],
|
||||
);
|
||||
|
||||
const resetSelectedOption = useCallback(() => {
|
||||
setSelectedOption(undefined);
|
||||
}, []);
|
||||
|
||||
const handleUnfoldedChange = useCallback(
|
||||
(nextIsUnfolded: boolean) => {
|
||||
setIsUnfolded(nextIsUnfolded);
|
||||
|
||||
if (!nextIsUnfolded) {
|
||||
handleViewNameSubmit();
|
||||
resetSelectedOption();
|
||||
}
|
||||
},
|
||||
[handleViewNameSubmit, resetSelectedOption],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
isUnfolded || viewEditMode.mode
|
||||
? setHotkeyScopeAndMemorizePreviousScope(HotkeyScope)
|
||||
: goBackToPreviousHotkeyScope();
|
||||
}, [
|
||||
HotkeyScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
isUnfolded,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
viewEditMode.mode,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
label="Options"
|
||||
isActive={false}
|
||||
isUnfolded={isUnfolded}
|
||||
onIsUnfoldedChange={setIsUnfolded}
|
||||
isUnfolded={isUnfolded || !!viewEditMode.mode}
|
||||
onIsUnfoldedChange={handleUnfoldedChange}
|
||||
HotkeyScope={HotkeyScope}
|
||||
>
|
||||
{!selectedOption && (
|
||||
<>
|
||||
<DropdownMenuHeader>View settings</DropdownMenuHeader>
|
||||
{!!viewEditMode.mode ? (
|
||||
<DropdownMenuInput
|
||||
ref={viewEditInputRef}
|
||||
autoFocus
|
||||
placeholder={
|
||||
viewEditMode.mode === 'create' ? 'New view' : 'View name'
|
||||
}
|
||||
defaultValue={
|
||||
viewEditMode.viewId
|
||||
? viewsById[viewEditMode.viewId]?.name
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<DropdownMenuHeader>View settings</DropdownMenuHeader>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItem
|
||||
onClick={() => setSelectedOption(Option.Properties)}
|
||||
onClick={() => handleSelectOption(Option.Properties)}
|
||||
>
|
||||
<IconTag size={theme.icon.size.md} />
|
||||
Properties
|
||||
|
@ -0,0 +1,149 @@
|
||||
import { type MouseEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { IconButton } from '@/ui/button/components/IconButton';
|
||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
||||
import { IconChevronDown, IconList, IconPencil, IconPlus } from '@/ui/icon';
|
||||
import {
|
||||
currentTableViewIdState,
|
||||
currentTableViewState,
|
||||
tableViewEditModeState,
|
||||
tableViewsState,
|
||||
} from '@/ui/table/states/tableViewsState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
|
||||
|
||||
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
`;
|
||||
|
||||
const StyledDropdownLabelAdornments = styled.span`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.grayScale.gray35};
|
||||
display: inline-flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledViewIcon = styled(IconList)`
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
type TableViewsDropdownButtonProps = {
|
||||
defaultViewName: string;
|
||||
HotkeyScope: TableViewsHotkeyScope;
|
||||
};
|
||||
|
||||
export const TableViewsDropdownButton = ({
|
||||
defaultViewName,
|
||||
HotkeyScope,
|
||||
}: TableViewsDropdownButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
const currentView = useRecoilScopedValue(
|
||||
currentTableViewState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const views = useRecoilScopedValue(tableViewsState, TableRecoilScopeContext);
|
||||
const [, setCurrentViewId] = useRecoilScopedState(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setViewEditMode = useSetRecoilState(tableViewEditModeState);
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleViewSelect = useCallback(
|
||||
(viewId?: string) => {
|
||||
setCurrentViewId(viewId);
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
[setCurrentViewId],
|
||||
);
|
||||
|
||||
const handleAddViewButtonClick = useCallback(() => {
|
||||
setViewEditMode({ mode: 'create', viewId: undefined });
|
||||
setIsUnfolded(false);
|
||||
}, [setViewEditMode]);
|
||||
|
||||
const handleEditViewButtonClick = useCallback(
|
||||
(event: MouseEvent<HTMLButtonElement>, viewId: string) => {
|
||||
event.stopPropagation();
|
||||
setViewEditMode({ mode: 'edit', viewId });
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
[setViewEditMode],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
isUnfolded
|
||||
? setHotkeyScopeAndMemorizePreviousScope(HotkeyScope)
|
||||
: goBackToPreviousHotkeyScope();
|
||||
}, [
|
||||
HotkeyScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
isUnfolded,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
label={
|
||||
<>
|
||||
<StyledViewIcon size={theme.icon.size.md} />
|
||||
{currentView?.name || defaultViewName}{' '}
|
||||
<StyledDropdownLabelAdornments>
|
||||
· {views.length + 1} <IconChevronDown size={theme.icon.size.sm} />
|
||||
</StyledDropdownLabelAdornments>
|
||||
</>
|
||||
}
|
||||
isActive={false}
|
||||
isUnfolded={isUnfolded}
|
||||
onIsUnfoldedChange={setIsUnfolded}
|
||||
anchor="left"
|
||||
HotkeyScope={HotkeyScope}
|
||||
>
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
<DropdownMenuItem onClick={() => handleViewSelect(undefined)}>
|
||||
<IconList size={theme.icon.size.md} />
|
||||
{defaultViewName}
|
||||
</DropdownMenuItem>
|
||||
{views.map((view) => (
|
||||
<DropdownMenuItem
|
||||
key={view.id}
|
||||
actions={
|
||||
<IconButton
|
||||
onClick={(event) => handleEditViewButtonClick(event, view.id)}
|
||||
icon={<IconPencil size={theme.icon.size.sm} />}
|
||||
/>
|
||||
}
|
||||
onClick={() => handleViewSelect(view.id)}
|
||||
>
|
||||
<IconList size={theme.icon.size.md} />
|
||||
{view.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
<DropdownMenuItem onClick={handleAddViewButtonClick}>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
Add view
|
||||
</DropdownMenuItem>
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</DropdownButton>
|
||||
);
|
||||
};
|
50
front/src/modules/ui/table/states/tableViewsState.ts
Normal file
50
front/src/modules/ui/table/states/tableViewsState.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { atom, atomFamily, selectorFamily } from 'recoil';
|
||||
|
||||
export type TableView = { id: string; name: string };
|
||||
|
||||
export const tableViewsState = atomFamily<TableView[], string>({
|
||||
key: 'tableViewsState',
|
||||
default: [],
|
||||
});
|
||||
|
||||
export const tableViewsByIdState = selectorFamily<
|
||||
Record<string, TableView>,
|
||||
string
|
||||
>({
|
||||
key: 'tableViewsByIdState',
|
||||
get:
|
||||
(scopeId) =>
|
||||
({ get }) =>
|
||||
get(tableViewsState(scopeId)).reduce<Record<string, TableView>>(
|
||||
(result, view) => ({ ...result, [view.id]: view }),
|
||||
{},
|
||||
),
|
||||
});
|
||||
|
||||
export const currentTableViewIdState = atomFamily<string | undefined, string>({
|
||||
key: 'currentTableViewIdState',
|
||||
default: undefined,
|
||||
});
|
||||
|
||||
export const currentTableViewState = selectorFamily<
|
||||
TableView | undefined,
|
||||
string
|
||||
>({
|
||||
key: 'currentTableViewState',
|
||||
get:
|
||||
(scopeId) =>
|
||||
({ get }) => {
|
||||
const currentViewId = get(currentTableViewIdState(scopeId));
|
||||
return currentViewId
|
||||
? get(tableViewsByIdState(scopeId))[currentViewId]
|
||||
: undefined;
|
||||
},
|
||||
});
|
||||
|
||||
export const tableViewEditModeState = atom<{
|
||||
mode: 'create' | 'edit' | undefined;
|
||||
viewId: string | undefined;
|
||||
}>({
|
||||
key: 'tableViewEditModeState',
|
||||
default: { mode: undefined, viewId: undefined },
|
||||
});
|
@ -1,5 +1,4 @@
|
||||
import { ReactNode, useCallback } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
@ -15,32 +14,26 @@ import { TableOptionsDropdownButton } from '@/ui/table/options/components/TableO
|
||||
import { TopBar } from '@/ui/top-bar/TopBar';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
|
||||
import { TableViewsDropdownButton } from '../../options/components/TableViewsDropdownButton';
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import type { TableView } from '../../states/tableViewsState';
|
||||
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
||||
import { TableViewsHotkeyScope } from '../../types/TableViewsHotkeyScope';
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
viewIcon?: ReactNode;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onColumnsChange?: (columns: ViewFieldDefinition<ViewFieldMetadata>[]) => void;
|
||||
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
|
||||
onViewsChange?: (views: TableView[]) => void;
|
||||
};
|
||||
|
||||
const StyledIcon = styled.div`
|
||||
display: flex;
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
& > svg {
|
||||
font-size: ${({ theme }) => theme.icon.size.sm};
|
||||
}
|
||||
`;
|
||||
|
||||
export function TableHeader<SortField>({
|
||||
viewName,
|
||||
viewIcon,
|
||||
availableSorts,
|
||||
onColumnsChange,
|
||||
onSortsUpdate,
|
||||
onViewsChange,
|
||||
}: OwnProps<SortField>) {
|
||||
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>(
|
||||
sortScopedState,
|
||||
@ -67,10 +60,10 @@ export function TableHeader<SortField>({
|
||||
return (
|
||||
<TopBar
|
||||
leftComponent={
|
||||
<>
|
||||
<StyledIcon>{viewIcon}</StyledIcon>
|
||||
{viewName}
|
||||
</>
|
||||
<TableViewsDropdownButton
|
||||
defaultViewName={viewName}
|
||||
HotkeyScope={TableViewsHotkeyScope.Dropdown}
|
||||
/>
|
||||
}
|
||||
displayBottomBorder={false}
|
||||
rightComponent={
|
||||
@ -90,7 +83,8 @@ export function TableHeader<SortField>({
|
||||
/>
|
||||
<TableOptionsDropdownButton
|
||||
onColumnsChange={onColumnsChange}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
onViewsChange={onViewsChange}
|
||||
HotkeyScope={TableOptionsHotkeyScope.Dropdown}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
export enum TableOptionsHotkeyScope {
|
||||
Dropdown = 'table-options-dropdown',
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export enum TableViewsHotkeyScope {
|
||||
Dropdown = 'table-views-dropdown',
|
||||
}
|
@ -4,13 +4,15 @@ import { v4 } from 'uuid';
|
||||
import { RecoilScopeContext } from '../states/RecoilScopeContext';
|
||||
|
||||
export function RecoilScope({
|
||||
SpecificContext,
|
||||
children,
|
||||
scopeId,
|
||||
SpecificContext,
|
||||
}: {
|
||||
SpecificContext?: Context<string | null>;
|
||||
children: React.ReactNode;
|
||||
scopeId?: string;
|
||||
SpecificContext?: Context<string | null>;
|
||||
}) {
|
||||
const currentScopeId = useRef(v4());
|
||||
const currentScopeId = useRef(scopeId || v4());
|
||||
|
||||
return SpecificContext ? (
|
||||
<SpecificContext.Provider value={currentScopeId.current}>
|
||||
|
9
front/src/modules/views/graphql/mutations/createViews.ts
Normal file
9
front/src/modules/views/graphql/mutations/createViews.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_VIEWS = gql`
|
||||
mutation CreateViews($data: [ViewCreateManyInput!]!) {
|
||||
createManyView(data: $data) {
|
||||
count
|
||||
}
|
||||
}
|
||||
`;
|
10
front/src/modules/views/graphql/mutations/updateView.ts
Normal file
10
front/src/modules/views/graphql/mutations/updateView.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_VIEW = gql`
|
||||
mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) {
|
||||
updateOneView(data: $data, where: $where) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
10
front/src/modules/views/graphql/queries/getViews.ts
Normal file
10
front/src/modules/views/graphql/queries/getViews.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_VIEWS = gql`
|
||||
query GetViews($where: ViewWhereInput) {
|
||||
views: findManyView(where: $where) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
@ -7,11 +7,13 @@ import {
|
||||
ViewFieldMetadata,
|
||||
ViewFieldTextMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import {
|
||||
tableColumnsByIdState,
|
||||
tableColumnsState,
|
||||
} from '@/ui/table/states/tableColumnsState';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import {
|
||||
SortOrder,
|
||||
useCreateViewFieldsMutation,
|
||||
@ -45,7 +47,10 @@ export const useTableViewFields = ({
|
||||
objectName: 'company' | 'person';
|
||||
viewFieldDefinitions: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||
}) => {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setColumns = useSetRecoilState(tableColumnsState);
|
||||
const columnsById = useRecoilValue(tableColumnsByIdState);
|
||||
|
||||
|
109
front/src/modules/views/hooks/useTableViews.ts
Normal file
109
front/src/modules/views/hooks/useTableViews.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { useCallback } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import {
|
||||
type TableView,
|
||||
tableViewsByIdState,
|
||||
tableViewsState,
|
||||
} from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import {
|
||||
useCreateViewsMutation,
|
||||
useGetViewsQuery,
|
||||
useUpdateViewMutation,
|
||||
ViewType,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
import { GET_VIEWS } from '../graphql/queries/getViews';
|
||||
|
||||
export const useTableViews = ({
|
||||
objectId,
|
||||
}: {
|
||||
objectId: 'company' | 'person';
|
||||
}) => {
|
||||
const [, setViews] = useRecoilScopedState(
|
||||
tableViewsState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const viewsById = useRecoilScopedValue(
|
||||
tableViewsByIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const [createViewsMutation] = useCreateViewsMutation();
|
||||
const [updateViewMutation] = useUpdateViewMutation();
|
||||
|
||||
const createViews = useCallback(
|
||||
(views: TableView[]) => {
|
||||
if (!views.length) return;
|
||||
|
||||
return createViewsMutation({
|
||||
variables: {
|
||||
data: views.map((view) => ({
|
||||
...view,
|
||||
objectId,
|
||||
type: ViewType.Table,
|
||||
})),
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
|
||||
});
|
||||
},
|
||||
[createViewsMutation, objectId],
|
||||
);
|
||||
|
||||
const updateViewFields = useCallback(
|
||||
(views: TableView[]) => {
|
||||
if (!views.length) return;
|
||||
|
||||
return Promise.all(
|
||||
views.map((view) =>
|
||||
updateViewMutation({
|
||||
variables: {
|
||||
data: { name: view.name },
|
||||
where: { id: view.id },
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_VIEWS) ?? ''],
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
[updateViewMutation],
|
||||
);
|
||||
|
||||
useGetViewsQuery({
|
||||
variables: {
|
||||
where: {
|
||||
objectId: { equals: objectId },
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
setViews(
|
||||
data.views.map((view) => ({
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
})),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const handleViewsChange = useCallback(
|
||||
async (nextViews: TableView[]) => {
|
||||
const viewsToCreate = nextViews.filter(
|
||||
(nextView) => !viewsById[nextView.id],
|
||||
);
|
||||
await createViews(viewsToCreate);
|
||||
|
||||
const viewsToUpdate = nextViews.filter(
|
||||
(nextView) =>
|
||||
viewsById[nextView.id] &&
|
||||
viewsById[nextView.id].name !== nextView.name,
|
||||
);
|
||||
await updateViewFields(viewsToUpdate);
|
||||
},
|
||||
[createViews, updateViewFields, viewsById],
|
||||
);
|
||||
|
||||
return { handleViewsChange };
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
import { Context, useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
sortsByKeyScopedState,
|
||||
@ -10,9 +9,10 @@ import type {
|
||||
SelectedSortType,
|
||||
SortType,
|
||||
} from '@/ui/filter-n-sort/types/interface';
|
||||
import { TableRecoilScopeContext } from '@/ui/table/states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { currentTableViewIdState } from '@/ui/table/states/tableViewsState';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { currentViewIdState } from '@/views/states/currentViewIdState';
|
||||
import {
|
||||
useCreateViewSortsMutation,
|
||||
useDeleteViewSortsMutation,
|
||||
@ -25,14 +25,25 @@ import { GET_VIEW_SORTS } from '../graphql/queries/getViewSorts';
|
||||
|
||||
export const useViewSorts = <SortField>({
|
||||
availableSorts,
|
||||
Context,
|
||||
}: {
|
||||
availableSorts: SortType<SortField>[];
|
||||
Context?: Context<string | null>;
|
||||
}) => {
|
||||
const currentViewId = useRecoilValue(currentViewIdState);
|
||||
const [, setSorts] = useRecoilScopedState(sortScopedState, Context);
|
||||
const sortsByKey = useRecoilScopedValue(sortsByKeyScopedState, Context);
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentTableViewIdState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [, setSorts] = useRecoilScopedState(
|
||||
sortScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const sortsByKey = useRecoilScopedValue(
|
||||
sortsByKeyScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentViewId) setSorts([]);
|
||||
}, [currentViewId, setSorts]);
|
||||
|
||||
useGetViewSortsQuery({
|
||||
skip: !currentViewId,
|
||||
@ -44,11 +55,19 @@ export const useViewSorts = <SortField>({
|
||||
onCompleted: (data) => {
|
||||
setSorts(
|
||||
data.viewSorts
|
||||
.map((viewSort) => ({
|
||||
...availableSorts.find((sort) => sort.key === viewSort.key),
|
||||
label: viewSort.name,
|
||||
order: viewSort.direction.toLowerCase(),
|
||||
}))
|
||||
.map((viewSort) => {
|
||||
const availableSort = availableSorts.find(
|
||||
(sort) => sort.key === viewSort.key,
|
||||
);
|
||||
|
||||
return availableSort
|
||||
? {
|
||||
...availableSort,
|
||||
label: viewSort.name,
|
||||
order: viewSort.direction.toLowerCase(),
|
||||
}
|
||||
: undefined;
|
||||
})
|
||||
.filter((sort): sort is SelectedSortType<SortField> => !!sort),
|
||||
);
|
||||
},
|
||||
|
@ -1,6 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const currentViewIdState = atom<string | undefined>({
|
||||
key: 'currentViewIdState',
|
||||
default: undefined,
|
||||
});
|
@ -68,7 +68,10 @@ export function Companies() {
|
||||
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
|
||||
onAddButtonClick={handleAddButtonClick}
|
||||
>
|
||||
<RecoilScope SpecificContext={TableRecoilScopeContext}>
|
||||
<RecoilScope
|
||||
scopeId="companies"
|
||||
SpecificContext={TableRecoilScopeContext}
|
||||
>
|
||||
<StyledTableContainer>
|
||||
<CompanyTable />
|
||||
</StyledTableContainer>
|
||||
|
@ -56,7 +56,7 @@ export function People() {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<RecoilScope SpecificContext={TableRecoilScopeContext}>
|
||||
<RecoilScope scopeId="people" SpecificContext={TableRecoilScopeContext}>
|
||||
<WithTopBarContainer
|
||||
title="People"
|
||||
icon={<IconUser size={theme.icon.size.sm} />}
|
||||
|
@ -110,6 +110,11 @@ import {
|
||||
UpdateViewSortAbilityHandler,
|
||||
DeleteViewSortAbilityHandler,
|
||||
} from './handlers/view-sort.ability-handler';
|
||||
import {
|
||||
CreateViewAbilityHandler,
|
||||
ReadViewAbilityHandler,
|
||||
UpdateViewAbilityHandler,
|
||||
} from './handlers/view.ability-handler';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
@ -194,14 +199,18 @@ import {
|
||||
CreatePipelineProgressAbilityHandler,
|
||||
UpdatePipelineProgressAbilityHandler,
|
||||
DeletePipelineProgressAbilityHandler,
|
||||
// ViewField
|
||||
ReadViewFieldAbilityHandler,
|
||||
CreateViewFieldAbilityHandler,
|
||||
UpdateViewFieldAbilityHandler,
|
||||
//Favorite
|
||||
ReadFavoriteAbilityHandler,
|
||||
CreateFavoriteAbilityHandler,
|
||||
DeleteFavoriteAbilityHandler,
|
||||
// View
|
||||
ReadViewAbilityHandler,
|
||||
CreateViewAbilityHandler,
|
||||
UpdateViewAbilityHandler,
|
||||
// ViewField
|
||||
ReadViewFieldAbilityHandler,
|
||||
CreateViewFieldAbilityHandler,
|
||||
UpdateViewFieldAbilityHandler,
|
||||
// ViewSort
|
||||
ReadViewSortAbilityHandler,
|
||||
CreateViewSortAbilityHandler,
|
||||
@ -288,14 +297,18 @@ import {
|
||||
CreatePipelineProgressAbilityHandler,
|
||||
UpdatePipelineProgressAbilityHandler,
|
||||
DeletePipelineProgressAbilityHandler,
|
||||
// ViewField
|
||||
ReadViewFieldAbilityHandler,
|
||||
CreateViewFieldAbilityHandler,
|
||||
UpdateViewFieldAbilityHandler,
|
||||
//Favorite
|
||||
ReadFavoriteAbilityHandler,
|
||||
CreateFavoriteAbilityHandler,
|
||||
DeleteFavoriteAbilityHandler,
|
||||
// View
|
||||
ReadViewAbilityHandler,
|
||||
CreateViewAbilityHandler,
|
||||
UpdateViewAbilityHandler,
|
||||
// ViewField
|
||||
ReadViewFieldAbilityHandler,
|
||||
CreateViewFieldAbilityHandler,
|
||||
UpdateViewFieldAbilityHandler,
|
||||
// ViewSort
|
||||
ReadViewSortAbilityHandler,
|
||||
CreateViewSortAbilityHandler,
|
||||
|
87
server/src/core/view/resolvers/view.resolver.ts
Normal file
87
server/src/core/view/resolvers/view.resolver.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma, Workspace } from '@prisma/client';
|
||||
|
||||
import { AppAbility } from 'src/ability/ability.factory';
|
||||
import {
|
||||
CreateViewAbilityHandler,
|
||||
ReadViewAbilityHandler,
|
||||
UpdateViewAbilityHandler,
|
||||
} from 'src/ability/handlers/view.ability-handler';
|
||||
import { FindManyViewArgs } from 'src/core/@generated/view/find-many-view.args';
|
||||
import { View } from 'src/core/@generated/view/view.model';
|
||||
import { ViewService } from 'src/core/view/services/view.service';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
PrismaSelect,
|
||||
PrismaSelector,
|
||||
} from 'src/decorators/prisma-select.decorator';
|
||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { UpdateOneViewArgs } from 'src/core/@generated/view/update-one-view.args';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||
import { CreateManyViewArgs } from 'src/core/@generated/view/create-many-view.args';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => View)
|
||||
export class ViewResolver {
|
||||
constructor(private readonly viewService: ViewService) {}
|
||||
|
||||
@Mutation(() => AffectedRows)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(CreateViewAbilityHandler)
|
||||
async createManyView(
|
||||
@Args() args: CreateManyViewArgs,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<AffectedRows> {
|
||||
return this.viewService.createMany({
|
||||
data: args.data.map((data) => ({
|
||||
...data,
|
||||
workspaceId: workspace.id,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [View])
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(ReadViewAbilityHandler)
|
||||
async findManyView(
|
||||
@Args() args: FindManyViewArgs,
|
||||
@UserAbility() ability: AppAbility,
|
||||
@PrismaSelector({ modelName: 'View' })
|
||||
prismaSelect: PrismaSelect<'View'>,
|
||||
): Promise<Partial<View>[]> {
|
||||
return this.viewService.findMany({
|
||||
where: args.where
|
||||
? {
|
||||
AND: [args.where, accessibleBy(ability).View],
|
||||
}
|
||||
: accessibleBy(ability).View,
|
||||
orderBy: args.orderBy,
|
||||
cursor: args.cursor,
|
||||
take: args.take,
|
||||
skip: args.skip,
|
||||
distinct: args.distinct,
|
||||
select: prismaSelect.value,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => View)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(UpdateViewAbilityHandler)
|
||||
async updateOneView(
|
||||
@Args() args: UpdateOneViewArgs,
|
||||
@PrismaSelector({ modelName: 'View' })
|
||||
prismaSelect: PrismaSelect<'View'>,
|
||||
): Promise<Partial<View>> {
|
||||
return this.viewService.update({
|
||||
data: args.data,
|
||||
where: args.where,
|
||||
select: prismaSelect.value,
|
||||
} as Prisma.ViewUpdateArgs);
|
||||
}
|
||||
}
|
39
server/src/core/view/services/view.service.ts
Normal file
39
server/src/core/view/services/view.service.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from 'src/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class ViewService {
|
||||
constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
// Find
|
||||
findFirst = this.prismaService.client.view.findFirst;
|
||||
findFirstOrThrow = this.prismaService.client.view.findFirstOrThrow;
|
||||
|
||||
findUnique = this.prismaService.client.view.findUnique;
|
||||
findUniqueOrThrow = this.prismaService.client.view.findUniqueOrThrow;
|
||||
|
||||
findMany = this.prismaService.client.view.findMany;
|
||||
|
||||
// Create
|
||||
create = this.prismaService.client.view.create;
|
||||
createMany = this.prismaService.client.view.createMany;
|
||||
|
||||
// Update
|
||||
update = this.prismaService.client.view.update;
|
||||
upsert = this.prismaService.client.view.upsert;
|
||||
updateMany = this.prismaService.client.view.updateMany;
|
||||
|
||||
// Delete
|
||||
delete = this.prismaService.client.view.delete;
|
||||
deleteMany = this.prismaService.client.view.deleteMany;
|
||||
|
||||
// Aggregate
|
||||
aggregate = this.prismaService.client.view.aggregate;
|
||||
|
||||
// Count
|
||||
count = this.prismaService.client.view.count;
|
||||
|
||||
// GroupBy
|
||||
groupBy = this.prismaService.client.view.groupBy;
|
||||
}
|
@ -4,11 +4,15 @@ import { ViewFieldService } from './services/view-field.service';
|
||||
import { ViewFieldResolver } from './resolvers/view-field.resolver';
|
||||
import { ViewSortService } from './services/view-sort.service';
|
||||
import { ViewSortResolver } from './resolvers/view-sort.resolver';
|
||||
import { ViewService } from './services/view.service';
|
||||
import { ViewResolver } from './resolvers/view.resolver';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
ViewService,
|
||||
ViewFieldService,
|
||||
ViewSortService,
|
||||
ViewResolver,
|
||||
ViewFieldResolver,
|
||||
ViewSortResolver,
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user