diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index d2e7f2255..b82a3daf4 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -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; updateOnePipelineProgress?: Maybe; updateOnePipelineStage?: Maybe; + updateOneView: View; updateOneViewField: ViewField; updateOneViewSort: ViewSort; updateUser: User; @@ -1019,6 +1021,12 @@ export type MutationCreateFavoriteForPersonArgs = { }; +export type MutationCreateManyViewArgs = { + data: Array; + skipDuplicates?: InputMaybe; +}; + + export type MutationCreateManyViewFieldArgs = { data: Array; skipDuplicates?: InputMaybe; @@ -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; findManyPipelineStage: Array; findManyUser: Array; + findManyView: Array; findManyViewField: Array; findManyViewSort: Array; findManyWorkspaceMember: Array; @@ -1955,6 +1970,16 @@ export type QueryFindManyUserArgs = { }; +export type QueryFindManyViewArgs = { + cursor?: InputMaybe; + distinct?: InputMaybe>; + orderBy?: InputMaybe>; + skip?: InputMaybe; + take?: InputMaybe; + where?: InputMaybe; +}; + + export type QueryFindManyViewFieldArgs = { cursor?: InputMaybe; distinct?: InputMaybe>; @@ -2283,6 +2308,13 @@ export type View = { type: ViewType; }; +export type ViewCreateManyInput = { + id?: InputMaybe; + name: Scalars['String']; + objectId: Scalars['String']; + type: ViewType; +}; + export type ViewCreateNestedOneWithoutFieldsInput = { connect?: InputMaybe; }; @@ -2361,6 +2393,12 @@ export type ViewFieldUpdateInput = { view?: InputMaybe; }; +export type ViewFieldUpdateManyWithoutViewNestedInput = { + connect?: InputMaybe>; + disconnect?: InputMaybe>; + set?: InputMaybe>; +}; + export type ViewFieldUpdateManyWithoutWorkspaceNestedInput = { connect?: InputMaybe>; disconnect?: InputMaybe>; @@ -2406,6 +2444,14 @@ export type ViewRelationFilter = { isNot?: InputMaybe; }; +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; }; +export type ViewSortUpdateManyWithoutViewNestedInput = { + connect?: InputMaybe>; + disconnect?: InputMaybe>; + set?: InputMaybe>; +}; + export type ViewSortUpdateManyWithoutWorkspaceNestedInput = { connect?: InputMaybe>; disconnect?: InputMaybe>; @@ -2491,6 +2543,15 @@ export enum ViewType { Table = 'Table' } +export type ViewUpdateInput = { + fields?: InputMaybe; + id?: InputMaybe; + name?: InputMaybe; + objectId?: InputMaybe; + sorts?: InputMaybe; + type?: InputMaybe; +}; + export type ViewUpdateManyWithoutWorkspaceNestedInput = { connect?: InputMaybe>; disconnect?: InputMaybe>; @@ -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; +}>; + + +export type CreateViewsMutation = { __typename?: 'Mutation', createManyView: { __typename?: 'AffectedRows', count: number } }; + export type CreateViewFieldsMutationVariables = Exact<{ data: Array | 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; +}>; + + +export type GetViewsQuery = { __typename?: 'Query', views: Array<{ __typename?: 'View', id: string, name: string }> }; + export type GetViewFieldsQueryVariables = Exact<{ where?: InputMaybe; orderBy?: InputMaybe | ViewFieldOrderByWithRelationInput>; @@ -5680,6 +5763,39 @@ export function useGetUsersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions; export type GetUsersLazyQueryHookResult = ReturnType; export type GetUsersQueryResult = Apollo.QueryResult; +export const CreateViewsDocument = gql` + mutation CreateViews($data: [ViewCreateManyInput!]!) { + createManyView(data: $data) { + count + } +} + `; +export type CreateViewsMutationFn = Apollo.MutationFunction; + +/** + * __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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateViewsDocument, options); + } +export type CreateViewsMutationHookResult = ReturnType; +export type CreateViewsMutationResult = Apollo.MutationResult; +export type CreateViewsMutationOptions = Apollo.BaseMutationOptions; 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; export type DeleteViewSortsMutationResult = Apollo.MutationResult; export type DeleteViewSortsMutationOptions = Apollo.BaseMutationOptions; +export const UpdateViewDocument = gql` + mutation UpdateView($data: ViewUpdateInput!, $where: ViewWhereUniqueInput!) { + updateOneView(data: $data, where: $where) { + id + name + } +} + `; +export type UpdateViewMutationFn = Apollo.MutationFunction; + +/** + * __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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateViewDocument, options); + } +export type UpdateViewMutationHookResult = ReturnType; +export type UpdateViewMutationResult = Apollo.MutationResult; +export type UpdateViewMutationOptions = Apollo.BaseMutationOptions; 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; export type UpdateViewSortMutationResult = Apollo.MutationResult; export type UpdateViewSortMutationOptions = Apollo.BaseMutationOptions; +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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetViewsDocument, options); + } +export function useGetViewsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetViewsDocument, options); + } +export type GetViewsQueryHookResult = ReturnType; +export type GetViewsLazyQueryHookResult = ReturnType; +export type GetViewsQueryResult = Apollo.QueryResult; export const GetViewFieldsDocument = gql` query GetViewFields($where: ViewFieldWhereInput, $orderBy: [ViewFieldOrderByWithRelationInput!]) { viewFields: findManyViewField(where: $where, orderBy: $orderBy) { diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index 00a02e433..5015aeffe 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -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() { /> } availableSorts={availableSorts} onColumnsChange={handleColumnsChange} onSortsUpdate={currentViewId ? updateSorts : undefined} + onViewsChange={handleViewsChange} updateEntityMutation={({ variables, }: { diff --git a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx index 83a1128bf..f58e266f1 100644 --- a/front/src/modules/companies/table/components/CompanyTableMockMode.tsx +++ b/front/src/modules/companies/table/components/CompanyTableMockMode.tsx @@ -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() { } availableSorts={availableSorts} updateEntityMutation={[useUpdateOneCompanyMutation()]} /> diff --git a/front/src/modules/people/table/components/PeopleTable.tsx b/front/src/modules/people/table/components/PeopleTable.tsx index d0e8bfbd7..d8af74c29 100644 --- a/front/src/modules/people/table/components/PeopleTable.tsx +++ b/front/src/modules/people/table/components/PeopleTable.tsx @@ -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() { /> } availableSorts={availableSorts} onColumnsChange={handleColumnsChange} onSortsUpdate={currentViewId ? updateSorts : undefined} + onViewsChange={handleViewsChange} updateEntityMutation={({ variables, }: { diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuInput.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuInput.tsx new file mode 100644 index 000000000..82736d152 --- /dev/null +++ b/front/src/modules/ui/dropdown/components/DropdownMenuInput.tsx @@ -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 +>((props, ref) => ( + + + +)); diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuSearch.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuSearch.tsx deleted file mode 100644 index d0419aa7d..000000000 --- a/front/src/modules/ui/dropdown/components/DropdownMenuSearch.tsx +++ /dev/null @@ -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, -) { - return ( - - - - ); -} diff --git a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx index 6526157d3..70aa313b9 100644 --- a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx +++ b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx @@ -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: () => ( - + @@ -269,7 +269,7 @@ export const Search: Story = { ...WithContentBelow, render: (args) => ( - + {mockSelectArray.map(({ name }) => ( diff --git a/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx index b8f41cea5..06fe27a00 100644 --- a/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx +++ b/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx @@ -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` } `; +const StyledDropdownMenuContainer = styled(DropdownMenuContainer)` + z-index: 2; +`; + function DropdownButton({ + anchor, label, isActive, children, @@ -99,9 +103,9 @@ function DropdownButton({ {label} {isUnfolded && ( - + {children} - + )} ); diff --git a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx index b1f01e1f6..2f32006a0 100644 --- a/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx +++ b/front/src/modules/ui/filter-n-sort/components/DropdownMenuContainer.tsx @@ -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; + +export function DropdownMenuContainer({ + anchor = 'right', + children, + onClose, + ...props +}: DropdownMenuContainerProps) { const dropdownRef = useRef(null); useListenClickOutside({ @@ -28,7 +37,7 @@ export function DropdownMenuContainer({ }); return ( - + {children} ); diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySearchInput.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySearchInput.tsx index fba02803e..b2a9254d6 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySearchInput.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownEntitySearchInput.tsx @@ -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 && ( - ) => { diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownTextSearchInput.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownTextSearchInput.tsx index 303adb4f5..6b62e90bf 100644 --- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownTextSearchInput.tsx +++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownTextSearchInput.tsx @@ -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 && ( - - - = { availableSorts?: Array>; onColumnsChange?: (columns: ViewFieldDefinition[]) => void; onSortsUpdate?: (sorts: Array>) => void; - onRowSelectionChange?: (rowSelection: string[]) => void; + onViewsChange?: (views: TableView[]) => void; updateEntityMutation: any; }; export function EntityTable({ viewName, - viewIcon, availableSorts, onColumnsChange, onSortsUpdate, + onViewsChange, updateEntityMutation, }: OwnProps) { const tableBodyRef = useRef(null); @@ -131,10 +132,10 @@ export function EntityTable({ diff --git a/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx b/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx index c5ebb98be..0c67e3211 100644 --- a/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx +++ b/front/src/modules/ui/table/options/components/TableOptionsDropdownButton.tsx @@ -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[]) => 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