From ed2094e8ff9bea8604264f2acad6ebaa04c812a0 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 19 Aug 2024 18:27:34 -0300 Subject: [PATCH 01/36] migrate user table --- app/react/V2/Routes/Settings/Users/Users.tsx | 74 ++++++---- .../Users/components/GroupFormSidepanel.tsx | 18 +-- .../Settings/Users/components/ListOfItems.tsx | 17 +-- .../Users/components/TableComponents.tsx | 131 +++++++++--------- .../Users/components/UserFormSidepanel.tsx | 21 ++- app/react/V2/Routes/Settings/Users/types.ts | 7 +- app/react/V2/api/users/index.ts | 15 +- 7 files changed, 145 insertions(+), 138 deletions(-) diff --git a/app/react/V2/Routes/Settings/Users/Users.tsx b/app/react/V2/Routes/Settings/Users/Users.tsx index b92507dc1b..52a73c3099 100644 --- a/app/react/V2/Routes/Settings/Users/Users.tsx +++ b/app/react/V2/Routes/Settings/Users/Users.tsx @@ -2,10 +2,8 @@ import React, { useRef, useState } from 'react'; import { IncomingHttpHeaders } from 'http'; import { ActionFunction, LoaderFunction, useFetcher, useLoaderData } from 'react-router-dom'; -import { Row } from '@tanstack/react-table'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; import { Translate } from 'app/I18N'; -import { Button, ConfirmationModal, Table_deprecated as Table, Tabs } from 'V2/Components/UI'; +import { Button, ConfirmationModal, Table, Tabs } from 'V2/Components/UI'; import * as usersAPI from 'V2/api/users'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; import { @@ -16,21 +14,22 @@ import { ListOfItems, } from './components'; import { useHandleNotifications } from './useHandleNotifications'; -import { FormIntent } from './types'; +import { FormIntent, User, Group } from './types'; type ActiveTab = 'Groups' | 'Users'; // eslint-disable-next-line max-statements const Users = () => { const { users, groups } = - (useLoaderData() as { users: ClientUserSchema[]; groups: ClientUserGroupSchema[] }) || []; + (useLoaderData() as { + users: User[]; + groups: Group[]; + }) || []; const [activeTab, setActiveTab] = useState('Users'); - const [selectedUsers, setSelectedUsers] = useState[]>([]); - const [selectedGroups, setSelectedGroups] = useState[]>([]); - const [sidepanelData, setSidepanelData] = useState< - ClientUserSchema | ClientUserGroupSchema | undefined - >(); + const [selectedUsers, setSelectedUsers] = useState([]); + const [selectedGroups, setSelectedGroups] = useState([]); + const [sidepanelData, setSidepanelData] = useState(); const [showSidepanel, setShowSidepanel] = useState(false); const [showConfirmationModal, setShowConfirmationModal] = useState(false); const [confirmationModalProps, setConfirmationModalProps] = useState({ @@ -43,12 +42,12 @@ const Users = () => { const fetcher = useFetcher(); useHandleNotifications(); - const usersTableColumns = getUsersColumns((user: ClientUserSchema) => { + const usersTableColumns = getUsersColumns((user: User) => { setShowSidepanel(true); setSidepanelData(user); }); - const groupsTableColumns = getGroupsColumns((group: ClientUserGroupSchema) => { + const groupsTableColumns = getGroupsColumns((group: Group) => { setShowSidepanel(true); setSidepanelData(group); }); @@ -58,9 +57,9 @@ const Users = () => { formData.set('intent', bulkActionIntent.current || ''); if (activeTab === 'Users') { - formData.set('data', JSON.stringify(selectedUsers.map(user => user.original))); + formData.set('data', JSON.stringify(selectedUsers)); } else { - formData.set('data', JSON.stringify(selectedGroups.map(group => group.original))); + formData.set('data', JSON.stringify(selectedGroups)); } formData.set('confirmation', password.current || ''); @@ -83,24 +82,36 @@ const Users = () => { }} > Users}> - - columns={usersTableColumns} + Users} - enableSelection - onSelection={setSelectedUsers} - initialState={{ sorting: [{ id: 'username', desc: false }] }} + columns={usersTableColumns} + header={ + + Users + + } + enableSelections + onChange={({ selectedRows }) => { + setSelectedUsers(() => users.filter(user => user.rowId in selectedRows)); + }} + defaultSorting={[{ id: 'username', desc: false }]} /> Groups}> - - columns={groupsTableColumns} +
Groups} - enableSelection - onSelection={setSelectedGroups} - initialState={{ sorting: [{ id: 'name', desc: false }] }} + columns={groupsTableColumns} + header={ + + Groups + + } + enableSelections + onChange={({ selectedRows }) => { + setSelectedGroups(() => groups.filter(group => group.rowId in selectedRows)); + }} + defaultSorting={[{ id: 'name', desc: false }]} /> @@ -177,7 +188,7 @@ const Users = () => { {activeTab === 'Users' ? ( { /> ) : ( { const usersLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => { - const users = await usersAPI.get(headers); - const groups = await usersAPI.getUserGroups(headers); + const users = (await usersAPI.get(headers)).map(user => ({ ...user, rowId: user._id! })); + const groups = (await usersAPI.getUserGroups(headers)).map(group => ({ + ...group, + rowId: group._id!, + })); return { users, groups }; }; diff --git a/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx b/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx index c3c3ab9ddb..faa6286c12 100644 --- a/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx +++ b/app/react/V2/Routes/Settings/Users/components/GroupFormSidepanel.tsx @@ -3,27 +3,21 @@ import React from 'react'; import { useForm } from 'react-hook-form'; import { useFetcher } from 'react-router-dom'; import { Translate } from 'app/I18N'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; import { Button, Card, Sidepanel } from 'V2/Components/UI'; import { InputField, MultiSelect } from 'V2/Components/Forms'; import { UserGroupSchema } from 'shared/types/userGroupType'; +import { User, Group } from '../types'; interface GroupFormSidepanelProps { showSidepanel: boolean; setShowSidepanel: React.Dispatch>; - setSelected: React.Dispatch< - React.SetStateAction - >; - selectedGroup?: ClientUserGroupSchema; - users?: ClientUserSchema[]; - groups?: ClientUserGroupSchema[]; + setSelected: React.Dispatch>; + selectedGroup?: Group; + users?: User[]; + groups?: Group[]; } -const isUnique = ( - name: string, - selectedGroup?: ClientUserGroupSchema, - userGroups?: ClientUserGroupSchema[] -) => +const isUnique = (name: string, selectedGroup?: Group, userGroups?: Group[]) => !userGroups?.find( userGroup => userGroup._id !== selectedGroup?._id && diff --git a/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx b/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx index f3215da900..ddad1b11c9 100644 --- a/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx +++ b/app/react/V2/Routes/Settings/Users/components/ListOfItems.tsx @@ -1,19 +1,14 @@ import React from 'react'; -import { Row } from '@tanstack/react-table'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; +import { User, Group } from '../types'; -const ListOfItems = ({ - items, -}: { - items: Row[] | Row[]; -}) => ( -
    +const ListOfItems = ({ items }: { items: User[] | Group[] }) => ( +
      {items.length ? items.map(item => - 'username' in item.original ? ( -
    • {item.original.username}
    • + 'username' in item ? ( +
    • {item.username}
    • ) : ( -
    • {item.original.name}
    • +
    • {item.name}
    • ) ) : null} diff --git a/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx index d37da40c6b..51738b7643 100644 --- a/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx @@ -5,7 +5,10 @@ import { LockClosedIcon } from '@heroicons/react/24/outline'; import { Tooltip } from 'flowbite-react'; import { Button, Pill } from 'app/V2/Components/UI'; import { t, Translate } from 'app/I18N'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; +import { User, Group } from '../types'; + +const userColumns = createColumnHelper(); +const groupColumns = createColumnHelper(); const UsernameHeader = () => Username; const GroupNameHeader = () => Name; @@ -15,7 +18,7 @@ const GroupsHeader = () => Group; const MembersHeader = () => Members; const ActionHeader = () => Action; -const ProtectionPill = ({ cell }: CellContext) => { +const ProtectionPill = ({ cell }: CellContext) => { if (cell.getValue()) { return ( @@ -30,7 +33,7 @@ const ProtectionPill = ({ cell }: CellContext) => { +const RolePill = ({ cell }: CellContext) => { const value = cell.getValue(); return (
      @@ -41,9 +44,7 @@ const RolePill = ({ cell }: CellContext) => ( +const MembersPill = ({ cell }: CellContext) => (
      {cell.getValue().map(member => (
      @@ -53,7 +54,7 @@ const MembersPill = ({
      ); -const GroupsPill = ({ cell }: CellContext) => ( +const GroupsPill = ({ cell }: CellContext) => (
      {cell.getValue()?.map(group => (
      @@ -65,12 +66,12 @@ const GroupsPill = ({ cell }: CellContext ); -const UsernameCell = ({ cell }: CellContext) => { - const userIsBlocker = cell.row.original.accountLocked; +const UsernameCell = ({ cell }: CellContext) => { + const userIsBlocked = cell.row.original.accountLocked; return ( -
      - {cell.getValue()} - {userIsBlocker && ( +
      + {cell.getValue()} + {userIsBlocked && ( ( ); -const EditUserButton = ({ cell }: CellContext) => { +const EditUserButton = ({ cell }: CellContext) => { const selectedUser = cell.row.original; return cell.column.columnDef.meta?.action?.(selectedUser)} />; }; -const EditUserGroupButton = ({ cell }: CellContext) => { +const EditUserGroupButton = ({ cell }: CellContext) => { const selectedUserGroup = cell.row.original; return cell.column.columnDef.meta?.action?.(selectedUserGroup)} />; }; -const getUsersColumns = (editButtonAction: (user: ClientUserSchema) => void) => { - const columnHelper = createColumnHelper(); - return [ - columnHelper.accessor('username', { - header: UsernameHeader, - cell: UsernameCell, - meta: { headerClassName: 'w-1/3' }, - }), - columnHelper.accessor('using2fa', { - header: ProtectionHeader, - cell: ProtectionPill, - meta: { headerClassName: 'w-0' }, - }), - columnHelper.accessor('role', { - header: RoleHeader, - cell: RolePill, - meta: { headerClassName: 'w-0' }, - }), - columnHelper.accessor('groups', { - header: GroupsHeader, - cell: GroupsPill, - meta: { headerClassName: 'w-2/3' }, - }), - columnHelper.display({ - id: '1', - header: ActionHeader, - cell: EditUserButton, - meta: { action: editButtonAction, headerClassName: 'sr-only invisible bg-gray-50' }, - enableSorting: false, - }), - ]; -}; +const getUsersColumns = (editButtonAction: (user: User) => void) => [ + userColumns.accessor('username', { + header: UsernameHeader, + cell: UsernameCell, + meta: { headerClassName: 'w-1/3' }, + }), + userColumns.accessor('using2fa', { + header: ProtectionHeader, + cell: ProtectionPill, + meta: { headerClassName: 'w-0' }, + }), + userColumns.accessor('role', { + header: RoleHeader, + cell: RolePill, + meta: { headerClassName: 'w-0' }, + }), + userColumns.accessor('groups', { + header: GroupsHeader, + cell: GroupsPill, + meta: { headerClassName: 'w-2/3' }, + }), + userColumns.display({ + id: '1', + header: ActionHeader, + cell: EditUserButton, + meta: { action: editButtonAction, headerClassName: 'sr-only' }, + enableSorting: false, + }), +]; -const getGroupsColumns = (editButtonAction: (group: ClientUserGroupSchema) => void) => { - const columnHelper = createColumnHelper(); - return [ - columnHelper.accessor('name', { - header: GroupNameHeader, - meta: { headerClassName: 'w-1/4' }, - }), - columnHelper.accessor('members', { - header: MembersHeader, - cell: MembersPill, - enableSorting: false, - meta: { headerClassName: 'w-3/4' }, - }), - columnHelper.display({ - id: '1', - header: ActionHeader, - cell: EditUserGroupButton, - meta: { action: editButtonAction, headerClassName: 'sr-only invisible bg-gray-50' }, - enableSorting: false, - }), - ]; -}; +const getGroupsColumns = (editButtonAction: (group: Group) => void) => [ + groupColumns.accessor('name', { + header: GroupNameHeader, + meta: { headerClassName: 'w-1/4' }, + }), + groupColumns.accessor('members', { + header: MembersHeader, + cell: MembersPill, + enableSorting: false, + meta: { headerClassName: 'w-3/4' }, + }), + groupColumns.display({ + id: '1', + header: ActionHeader, + cell: EditUserGroupButton, + meta: { action: editButtonAction, headerClassName: 'sr-only' }, + enableSorting: false, + }), +]; export { getUsersColumns, getGroupsColumns }; diff --git a/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx b/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx index be5197445d..acd63c9e65 100644 --- a/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx +++ b/app/react/V2/Routes/Settings/Users/components/UserFormSidepanel.tsx @@ -6,25 +6,23 @@ import { useForm } from 'react-hook-form'; import { useFetcher } from 'react-router-dom'; import { FetchResponseError } from 'shared/JSONRequest'; import { t, Translate } from 'app/I18N'; -import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; import { InputField, Select, MultiSelect } from 'V2/Components/Forms'; import { Button, Card, ConfirmationModal, Sidepanel } from 'V2/Components/UI'; import { validEmailFormat } from 'V2/shared/formatHelpers'; import { UserRole } from 'shared/types/userSchema'; import { QuestionMarkCircleIcon } from '@heroicons/react/20/solid'; import { PermissionsListModal } from './PermissionsListModal'; +import { User, Group } from '../types'; type SubmitType = 'formSubmit' | 'reset-2fa' | 'unlock-user' | 'reset-password' | undefined; interface UserFormSidepanelProps { showSidepanel: boolean; setShowSidepanel: React.Dispatch>; - setSelected: React.Dispatch< - React.SetStateAction - >; - selectedUser?: ClientUserSchema; - users?: ClientUserSchema[]; - groups?: ClientUserGroupSchema[]; + setSelected: React.Dispatch>; + selectedUser?: User; + users?: User[]; + groups?: Group[]; } const userRoles = [ @@ -36,7 +34,7 @@ const userRoles = [ }, ]; -const isUnique = (nameVal: string, selectedUser?: ClientUserSchema, users?: ClientUserSchema[]) => +const isUnique = (nameVal: string, selectedUser?: User, users?: User[]) => !users?.find( existingUser => existingUser._id !== selectedUser?._id && @@ -44,7 +42,7 @@ const isUnique = (nameVal: string, selectedUser?: ClientUserSchema, users?: Clie existingUser.email?.trim().toLowerCase() === nameVal.trim().toLowerCase()) ); -const calculateSelectedGroups = (selectedGroups: string[], groups?: ClientUserGroupSchema[]) => +const calculateSelectedGroups = (selectedGroups: string[], groups?: Group[]) => selectedGroups.map(selectedGroup => { const group = groups?.find(originalGroup => originalGroup.name === selectedGroup); return { _id: group?._id as string, name: group?.name as string }; @@ -110,7 +108,8 @@ const UserFormSidepanel = ({ password: '', role: 'collaborator', groups: [], - } as ClientUserSchema); + rowId: 'NEW', + } as User); const { register, @@ -140,7 +139,7 @@ const UserFormSidepanel = ({ } }, [fetcher]); - const formSubmit = async (data: ClientUserSchema) => { + const formSubmit = async (data: User) => { const formData = new FormData(); if (data._id) { formData.set('intent', 'edit-user'); diff --git a/app/react/V2/Routes/Settings/Users/types.ts b/app/react/V2/Routes/Settings/Users/types.ts index a46d61becf..ee77b1e402 100644 --- a/app/react/V2/Routes/Settings/Users/types.ts +++ b/app/react/V2/Routes/Settings/Users/types.ts @@ -1,3 +1,5 @@ +import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; + type FormIntent = | 'new-user' | 'edit-user' @@ -11,4 +13,7 @@ type FormIntent = | 'bulk-reset-2fa' | 'bulk-reset-password'; -export type { FormIntent }; +type User = ClientUserSchema & { rowId: string }; +type Group = ClientUserGroupSchema & { rowId: string }; + +export type { FormIntent, User, Group }; diff --git a/app/react/V2/api/users/index.ts b/app/react/V2/api/users/index.ts index d0368b3144..6dcb66bc3a 100644 --- a/app/react/V2/api/users/index.ts +++ b/app/react/V2/api/users/index.ts @@ -4,8 +4,9 @@ import api from 'app/utils/api'; import { RequestParams } from 'app/utils/RequestParams'; import { ClientUserGroupSchema, ClientUserSchema } from 'app/apiResponseTypes'; -const prepareUser = (user: ClientUserSchema) => { +const prepareUser = (user: ClientUserSchema & { rowId?: string }) => { const preparedUser = { ...user }; + delete preparedUser.rowId; if (!preparedUser.password) { delete preparedUser.password; @@ -74,9 +75,13 @@ const deleteUser = async ( } }; -const saveGroup = async (group: ClientUserGroupSchema, headers?: IncomingHttpHeaders) => { +const saveGroup = async ( + group: ClientUserGroupSchema & { rowId?: string }, + headers?: IncomingHttpHeaders +) => { try { - const requestParams = new RequestParams(group, headers); + const { rowId, ...groupToSave } = group; + const requestParams = new RequestParams(groupToSave, headers); const response = await api.post('usergroups', requestParams); return response.json; } catch (e) { @@ -169,7 +174,7 @@ const reset2FA = async ( } }; -const get = async (headers?: IncomingHttpHeaders) => { +const get = async (headers?: IncomingHttpHeaders): Promise => { try { const requestParams = new RequestParams({}, headers); const response = await UsersAPI.get(requestParams); @@ -189,7 +194,7 @@ const getCurrentUser = async (headers?: IncomingHttpHeaders) => { } }; -const getUserGroups = async (headers?: IncomingHttpHeaders) => { +const getUserGroups = async (headers?: IncomingHttpHeaders): Promise => { try { const requestParams = new RequestParams({}, headers); const response = await api.get('usergroups', requestParams); From 720bb5d30839f9d854f95175a90af16cbfe567fc Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 19 Aug 2024 18:54:39 -0300 Subject: [PATCH 02/36] migrate translations table --- .../Translations/TranslationsList.tsx | 46 ++++++++++++------ .../components/TableComponents.tsx | 6 +-- ...slations list should be accessible #0.png | Bin 75223 -> 53883 bytes 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx b/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx index 829eb54ed6..f0929a165c 100644 --- a/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx +++ b/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { IncomingHttpHeaders } from 'http'; import { useLoaderData, LoaderFunction } from 'react-router-dom'; -import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; +import { createColumnHelper } from '@tanstack/react-table'; import { Translate } from 'app/I18N'; import { ClientTranslationContextSchema, ClientTranslationSchema } from 'app/istore'; -import { Table_deprecated as Table } from 'V2/Components/UI'; +import { Table } from 'V2/Components/UI'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; import * as translationsAPI from 'V2/api/translations/index'; import { @@ -15,6 +15,10 @@ import { ActionHeader, } from './components/TableComponents'; +type TranslationContext = ClientTranslationContextSchema & { rowId: string }; + +const columnHelper = createColumnHelper(); + const translationsListLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => @@ -24,12 +28,15 @@ const TranslationsList = () => { const translations = useLoaderData() as ClientTranslationSchema[]; const contexts: { - systemContexts: ClientTranslationContextSchema[]; - contentContexts: ClientTranslationContextSchema[]; + systemContexts: TranslationContext[]; + contentContexts: TranslationContext[]; } = { systemContexts: [], contentContexts: [] }; translations[0]?.contexts?.forEach(context => { - const contextTranslations = { ...context }; + const contextTranslations: TranslationContext = { + ...context, + rowId: context.id!, + }; if (!contextTranslations.values || Object.keys(contextTranslations.values).length === 0) { return undefined; @@ -42,24 +49,22 @@ const TranslationsList = () => { return contexts.contentContexts.push({ ...contextTranslations }); }); - // Helper typed as any because of https://github.com/TanStack/table/issues/4224 - const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor('label', { header: LabelHeader, meta: { headerClassName: 'w-1/3' }, - }) as ColumnDef, + }), columnHelper.accessor('type', { header: TypeHeader, cell: ContextPill, meta: { headerClassName: 'w-2/3' }, - }) as ColumnDef, + }), columnHelper.accessor('id', { header: ActionHeader, cell: RenderButton, enableSorting: false, meta: { headerClassName: 'sr-only invisible bg-gray-50' }, - }) as ColumnDef, + }), ]; return ( @@ -72,19 +77,27 @@ const TranslationsList = () => {
      - +
System translations} - initialState={{ sorting: [{ id: 'label', desc: false }] }} + header={ + + System translations + + } + defaultSorting={[{ id: 'label', desc: false }]} />
- +
Content translations} - initialState={{ sorting: [{ id: 'label', desc: false }] }} + header={ + + Content translations + + } + defaultSorting={[{ id: 'label', desc: false }]} /> @@ -93,4 +106,5 @@ const TranslationsList = () => { ); }; +export type { TranslationContext }; export { TranslationsList, translationsListLoader }; diff --git a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx index 395b4bc4b5..0923c5be60 100644 --- a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx @@ -3,14 +3,14 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { CellContext } from '@tanstack/react-table'; import { Translate } from 'app/I18N'; -import { ClientTranslationContextSchema } from 'app/istore'; import { Button, Pill } from 'V2/Components/UI'; +import { TranslationContext } from '../TranslationsList'; const LabelHeader = () => Name; const TypeHeader = () => Type; const ActionHeader = () => Action; -const RenderButton = ({ cell }: CellContext) => ( +const RenderButton = ({ cell }: CellContext) => (
Pages} - onSelection={setSelectedPages} - initialState={{ sorting: [{ id: 'title', desc: false }] }} + enableSelections + header={ + + Pages + + } + onChange={({ selectedRows }) => { + setSelectedPages(pages.filter(page => page.rowId in selectedRows)); + }} + defaultSorting={[{ id: 'title', desc: false }]} /> @@ -146,4 +154,6 @@ const PagesList = () => { ); }; + +export type { TablePage }; export { PagesList, pagesListLoader }; diff --git a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx index 4ad8ffcf0f..75bb8c5851 100644 --- a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx +++ b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { kebabCase } from 'lodash'; -import { CellContext, Row } from '@tanstack/react-table'; -import { Page } from 'app/V2/shared/types'; +import { CellContext } from '@tanstack/react-table'; import { Button, Pill } from 'app/V2/Components/UI'; import { Translate } from 'app/I18N'; +import { TablePage } from '../PagesList'; const getPageUrl = (sharedId: string, title: string) => `page/${sharedId}/${kebabCase(title)}`; @@ -14,7 +14,7 @@ const TitleHeader = () => Title; const UrlHeader = () => URL; const ActionHeader = () => Action; -const ActionCell = ({ cell }: CellContext) => { +const ActionCell = ({ cell }: CellContext) => { const pageUrl = getPageUrl(cell.getValue(), cell.row.original.title); const isEntityView = cell.row.original.entityView; @@ -38,7 +38,7 @@ const ActionCell = ({ cell }: CellContext) => { ); }; -const YesNoPill = ({ cell }: CellContext) => { +const YesNoPill = ({ cell }: CellContext) => { const { color, label }: { color: 'primary' | 'gray'; label: React.ReactElement } = cell.getValue() ? { color: 'primary', label: Yes } : { color: 'gray', label: No }; @@ -46,19 +46,18 @@ const YesNoPill = ({ cell }: CellContext) => { return {label}; }; -const UrlCell = ({ cell }: CellContext) => { +const UrlCell = ({ cell }: CellContext) => { const sharedId = cell.getValue(); const { title } = cell.row.original; const url = `/${getPageUrl(sharedId, title)}`; return url; }; -const List = ({ items }: { items: Row[] }) => ( +const List = ({ items }: { items: TablePage[] }) => (
    - {items.map(item => { - const page = item.original; - return
  • {page.title}
  • ; - })} + {items.map(item => ( +
  • {item.title}
  • + ))}
); diff --git a/app/react/V2/api/pages/index.ts b/app/react/V2/api/pages/index.ts index 3a8037879e..b51f5407fd 100644 --- a/app/react/V2/api/pages/index.ts +++ b/app/react/V2/api/pages/index.ts @@ -4,7 +4,7 @@ import { RequestParams } from 'app/utils/RequestParams'; import { Page } from 'V2/shared/types'; import { FetchResponseError } from 'shared/JSONRequest'; -const get = async (language: string, headers?: IncomingHttpHeaders): Promise => { +const get = async (language: string, headers?: IncomingHttpHeaders): Promise => { try { const requestParams = new RequestParams({}, headers); api.locale(language); From be6d9a7c727e3c38fdeb6e381001464a3c88ce60 Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 20 Aug 2024 10:26:58 -0300 Subject: [PATCH 04/36] migrate language list --- .../Settings/Languages/LanguagesList.tsx | 34 ++++++++++++------- .../Languages/components/TableComponents.tsx | 8 ++--- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx b/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx index 607ae42214..d122a99742 100644 --- a/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx +++ b/app/react/V2/Routes/Settings/Languages/LanguagesList.tsx @@ -4,11 +4,11 @@ import { IncomingHttpHeaders } from 'http'; import { useLoaderData, LoaderFunction } from 'react-router-dom'; import { useAtomValue } from 'jotai'; import { intersectionBy, keyBy, merge, values } from 'lodash'; -import { ColumnDef, Row, createColumnHelper } from '@tanstack/react-table'; +import { Row, createColumnHelper } from '@tanstack/react-table'; import { Translate, I18NApi, t } from 'app/I18N'; import { RequestParams } from 'app/utils/RequestParams'; import { settingsAtom } from 'app/V2/atoms/settingsAtom'; -import { Button, Table_deprecated as Table, ConfirmationModal } from 'V2/Components/UI'; +import { Button, Table, ConfirmationModal } from 'V2/Components/UI'; import { useApiCaller } from 'V2/CustomHooks/useApiCaller'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; import { LanguageSchema } from 'shared/types/commonTypes'; @@ -24,10 +24,14 @@ import { LanguageLabel, } from './components/TableComponents'; +type TableLanguages = LanguageSchema & { rowId: string }; +const columnHelper = createColumnHelper(); + const languagesListLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => I18NApi.getLanguages(new RequestParams({}, headers)); + // eslint-disable-next-line max-statements const LanguagesList = () => { const { languages: collectionLanguages = [] } = useAtomValue(settingsAtom); @@ -41,9 +45,10 @@ const LanguagesList = () => { const notInstalledLanguages = availableLanguages.filter( l => !collectionLanguages.find(cl => cl.key === l.key) ); - const languages = values( + + const languages: TableLanguages[] = values( merge(keyBy(installedLanguages, 'key'), keyBy(collectionLanguages, 'key')) - ); + ).map(lang => ({ ...lang, rowId: lang._id! })); const handleAction = ( @@ -121,33 +126,31 @@ const LanguagesList = () => { ); }; - // Helper typed as any because of https://github.com/TanStack/table/issues/4224 - const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor('label', { id: 'label', header: LabelHeader, cell: LanguageLabel, meta: { headerClassName: 'w-9/12' }, - }) as ColumnDef, + }), columnHelper.accessor('default', { header: DefaultHeader, cell: DefaultButton, enableSorting: false, meta: { action: setDefaultLanguage, headerClassName: 'text-center w-1/12' }, - }) as ColumnDef, + }), columnHelper.accessor('key', { header: ResetHeader, cell: ResetButton, enableSorting: false, meta: { action: resetModal, headerClassName: 'text-center w-1/12' }, - }) as ColumnDef, + }), columnHelper.accessor('_id', { header: UninstallHeader, cell: UninstallButton, enableSorting: false, meta: { action: uninstallModal, headerClassName: 'text-center w-1/12' }, - }) as ColumnDef, + }), ]; return ( @@ -160,11 +163,15 @@ const LanguagesList = () => {
- +
Active languages} - initialState={{ sorting: [{ id: 'label', desc: false }] }} + header={ + + Active languages + + } + defaultSorting={[{ id: 'label', desc: false }]} /> @@ -195,4 +202,5 @@ const LanguagesList = () => { ); }; +export type { TableLanguages }; export { LanguagesList, languagesListLoader }; diff --git a/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx index aee9b7d7ad..71b23bb6fa 100644 --- a/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Languages/components/TableComponents.tsx @@ -4,9 +4,9 @@ import { StarIcon } from '@heroicons/react/20/solid'; import { Translate } from 'app/I18N'; import { Button } from 'V2/Components/UI/Button'; import { CellContext } from '@tanstack/react-table'; -import { LanguageSchema } from 'shared/types/commonTypes'; +import { TableLanguages } from '../LanguagesList'; -const DefaultButton = ({ cell, column }: CellContext) => ( +const DefaultButton = ({ cell, column }: CellContext) => ( ); -const UninstallButton = ({ cell, column }: CellContext) => +const UninstallButton = ({ cell, column }: CellContext) => !cell.row.original.default ? (
{ setShowSipanel(true); setFileToEdit(file); })} - data={files} - title={Custom Uploads} + onChange={({ selectedRows: selected }) => { + setSelectedRows(files.filter(file => file.rowId in selected)); + }} + enableSelections + header={ + + Custom Uploads + + } /> @@ -184,4 +189,5 @@ const CustomUploads = () => { ); }; +export type { CustomUpload }; export { CustomUploads, customUploadsLoader }; diff --git a/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx b/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx index c6475a5624..261ee75166 100644 --- a/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx +++ b/app/react/V2/Routes/Settings/CustomUploads/components/EditFileSidepanel.tsx @@ -3,19 +3,20 @@ import React from 'react'; import { useForm } from 'react-hook-form'; import { useRevalidator } from 'react-router-dom'; import { useSetAtom } from 'jotai'; -import { Translate } from 'app/I18N'; import { FileType } from 'shared/types/fileType'; +import { Translate } from 'app/I18N'; import { FetchResponseError } from 'shared/JSONRequest'; import { Button, Card, Sidepanel } from 'V2/Components/UI'; import { InputField } from 'V2/Components/Forms'; import { getFileNameAndExtension } from 'V2/shared/formatHelpers'; import { notificationAtom } from 'V2/atoms'; import { update } from 'V2/api/files'; +import { CustomUpload } from '../CustomUploads'; type EditFileSidepanelProps = { showSidepanel: boolean; closeSidepanel: () => any; - file?: FileType; + file?: CustomUpload; }; const EditFileSidepanel = ({ showSidepanel, closeSidepanel, file }: EditFileSidepanelProps) => { @@ -52,7 +53,8 @@ const EditFileSidepanel = ({ showSidepanel, closeSidepanel, file }: EditFileSide }); const save = async (data: { filename: string } | { filename: undefined }) => { - const updatedFile: FileType = { ...file, originalname: `${data.filename}.${extension}` }; + const updatedFile = { ...file, originalname: `${data.filename}.${extension}` }; + delete updatedFile.rowId; const response = await update(updatedFile); closeSidepanel(); notify(response); diff --git a/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx b/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx index d8b34ee11a..18b94d2580 100644 --- a/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx +++ b/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx @@ -1,20 +1,20 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { FileType } from 'shared/types/fileType'; import { Translate } from 'app/I18N'; import { Button, FileIcon } from 'V2/Components/UI'; +import { CustomUpload } from '../CustomUploads'; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); const TitleHeader = () => Name; const PreviewHeader = () => Preview; const URLHeader = () => URL; const ActionHeader = () => Action; -const TitleCell = ({ getValue }: CellContext) => getValue(); -const URLCell = ({ getValue }: CellContext) => `/assets/${getValue()}`; -const PreviewCell = ({ cell }: CellContext) => { +const TitleCell = ({ getValue }: CellContext) => getValue(); +const URLCell = ({ getValue }: CellContext) => `/assets/${getValue()}`; +const PreviewCell = ({ cell }: CellContext) => { const { mimetype = '', originalname, filename } = cell.row.original; return (
@@ -27,7 +27,8 @@ const PreviewCell = ({ cell }: CellContext) => {
); }; -const ActionCell = ({ cell }: CellContext) => { + +const ActionCell = ({ cell }: CellContext) => { const actions = cell.column.columnDef.meta?.action ? cell.column.columnDef.meta?.action() : undefined; @@ -55,8 +56,8 @@ const ActionCell = ({ cell }: CellContext) => { }; const createColumns = ( - handleDelete: (file: FileType) => void, - editFile: (file: FileType) => void + handleDelete: (file: CustomUpload) => void, + editFile: (file: CustomUpload) => void ) => [ columnHelper.display({ id: 'preview', From d523c59d174b69153010c13ef81171e18d0bf8dd Mon Sep 17 00:00:00 2001 From: Santiago Date: Tue, 20 Aug 2024 10:56:07 -0300 Subject: [PATCH 06/36] migrated activity log table --- .../Settings/ActivityLog/ActivityLog.tsx | 23 +++++++++---------- .../Settings/ActivityLog/ActivityLogLoader.ts | 9 +++++--- .../ActivityLog/components/TableElements.tsx | 23 ++++++++++--------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx b/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx index c69f7d20a8..3223730f4d 100644 --- a/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx +++ b/app/react/V2/Routes/Settings/ActivityLog/ActivityLog.tsx @@ -7,13 +7,7 @@ import { useAtomValue } from 'jotai'; import { Translate } from 'app/I18N'; import { ClientSettings } from 'app/apiResponseTypes'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; -import { - Button, - PaginationState, - Paginator, - Pill, - Table_deprecated as Table, -} from 'app/V2/Components/UI'; +import { Button, PaginationState, Paginator, Pill, Table } from 'app/V2/Components/UI'; import { useIsFirstRender } from 'app/V2/CustomHooks/useIsFirstRender'; import { settingsAtom } from 'app/V2/atoms'; import { ActivityLogEntryType } from 'shared/types/activityLogEntryType'; @@ -98,12 +92,17 @@ const ActivityLog = () => { {error === undefined && ( - - title={Activity Log} - columns={columns} +
{ + setSorting(sortingState); + }} + header={ + + Activity Log + + } footer={
({ ...row, rowId: row._id })), totalPages, page: params.page, total: activityLogList.totalRows, @@ -153,6 +155,7 @@ const filterPairs = (filters: ActivityLogSearch) => { }); return _(plainFilters).sortBy(0).value(); }; + const updateSearch = ( filters: ActivityLogSearch, searchParams: URLSearchParams, @@ -197,7 +200,7 @@ const buildPageURL = (appliedFilters: any, pageTo: string | number, location: Lo return `${location.pathname}?${createSearchParams(newParams)}`; }; -export type { LoaderData, ActivityLogSearch }; +export type { LoaderData, ActivityLogSearch, LogEntry }; export { activityLogLoader, getAppliedFilters, diff --git a/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx b/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx index 5cde237df8..c92e6aa233 100644 --- a/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx +++ b/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx @@ -6,14 +6,15 @@ import { Tooltip } from 'flowbite-react'; import { Pill, Button } from 'app/V2/Components/UI'; import type { PillColor } from 'app/V2/Components/UI'; import { Translate } from 'app/I18N'; -import { ActivityLogEntryType, ActivityLogSemanticType } from 'shared/types/activityLogEntryType'; +import { ActivityLogSemanticType } from 'shared/types/activityLogEntryType'; +import { LogEntry } from '../ActivityLogLoader'; const ActionHeader = () => Action; const UserHeader = () => User; const DescriptionHeader = () => Description; const TimeHeader = () => Timestamp; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); type Methods = 'CREATE' | 'UPDATE' | 'DELETE' | 'RAW' | 'MIGRATE' | 'WARNING'; const methodColors: Map = new Map([ @@ -33,15 +34,15 @@ const ActionPill = ({ action, className = '' }: { action: string; className?: st ); }; -const ActionCell = ({ cell }: CellContext) => ( +const ActionCell = ({ cell }: CellContext) => ( ); -const UserCell = ({ cell }: CellContext) => ( +const UserCell = ({ cell }: CellContext) => ( {cell.getValue()} ); -const DescriptionCell = ({ cell }: CellContext) => { +const DescriptionCell = ({ cell }: CellContext) => { const semanticData = cell.getValue(); return ( @@ -49,16 +50,16 @@ const DescriptionCell = ({ cell }: CellContext -
+
Query {cell.row.original.query}
-
+
Body - + {cell.row.original.body}
@@ -89,7 +90,7 @@ const DescriptionCell = ({ cell }: CellContext - ({ cell }: CellContext) => { + ({ cell }: CellContext) => { const date = moment(cell.getValue()); return ( <> @@ -99,7 +100,7 @@ const TimeCell = ); }; -const ViewCell = ({ cell, column }: CellContext) => ( +const ViewCell = ({ cell, column }: CellContext) => (
{ - setLinkChanges(data); + data={linkState} + onChange={({ rows, selectedRows }) => { + setLinkState(rows); + setSelectedLinks(selectedRows); }} - title={Menu} - subRowsKey="sublinks" - onSelection={setSelectedLinks} + header={ + + Menu + + } /> - - {selectedLinks.length > 0 && ( + + {Object.keys(selectedLinks).length > 0 && (
- Selected {selectedLinks.length} of{' '} - {links?.reduce( - (acc, link) => acc + (link.type === 'group' ? (link.sublinks?.length || 1) + 1 : 1), + Selected {Object.keys(selectedLinks).length}  + of  + {linkState.reduce( + (acc, link) => acc + (link.type === 'group' ? (link.subRows?.length || 1) + 1 : 1), 0 )}
)} - {selectedLinks.length === 0 && ( + {Object.keys(selectedLinks).length === 0 && (
); }; -export { MenuForm }; +export { MenuForm, updateLinks }; diff --git a/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx b/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx index d01121b909..cbf849acf9 100644 --- a/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx @@ -1,12 +1,11 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { Translate } from 'app/I18N'; -import { CellContext, ColumnDef, createColumnHelper } from '@tanstack/react-table'; -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; -import { EmbededButton, Button } from 'app/V2/Components/UI'; -import { ClientSettingsLinkSchema } from 'app/apiResponseTypes'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; +import { Button } from 'app/V2/Components/UI'; +import { Link } from '../MenuConfig'; -const EditButton = ({ cell, column }: CellContext) => ( +const EditButton = ({ cell, column }: CellContext) => (
Relationship types} - onSelection={setSelectedItems} + header={ + + Relationship types + + } + onChange={({ selectedRows }) => { + setSelectedItems( + tableRelationshipTypes.filter(relationship => relationship.rowId in selectedRows) + ); + }} /> @@ -165,7 +169,7 @@ const RelationshipTypes = () => { withOverlay >
setIsSidepanelOpen(false)} currentTypes={relationshipTypes} submit={submit} @@ -179,7 +183,7 @@ const RelationshipTypes = () => { body={
    {selectedItems.map(item => ( -
  • {item.original.name}
  • +
  • {item.name}
  • ))}
} diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx index 1a442b6212..08642ba9e2 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/components/Form.tsx @@ -4,14 +4,14 @@ import React from 'react'; import { Translate } from 'app/I18N'; import { InputField } from 'app/V2/Components/Forms'; import { useForm } from 'react-hook-form'; -import { ClientRelationshipType } from 'app/apiResponseTypes'; import { Button, Card } from 'app/V2/Components/UI'; +import { Relationships } from './TableComponents'; interface FormProps { closePanel: () => void; - relationtype?: ClientRelationshipType; - submit: (formValues: ClientRelationshipType) => void; - currentTypes: ClientRelationshipType[]; + relationtype?: Relationships; + submit: (formValues: Relationships) => void; + currentTypes: Relationships[]; } const Form = ({ closePanel, submit, relationtype, currentTypes }: FormProps) => { @@ -19,7 +19,7 @@ const Form = ({ closePanel, submit, relationtype, currentTypes }: FormProps) => register, handleSubmit, formState: { errors }, - } = useForm({ + } = useForm({ values: relationtype, mode: 'onSubmit', }); @@ -47,7 +47,7 @@ const Form = ({ closePanel, submit, relationtype, currentTypes }: FormProps) => -
+
{ setSorting(sortingState); }} From f58cb168a389d341b3e33d57aaea3b61df0174de Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 22 Aug 2024 16:44:20 -0300 Subject: [PATCH 10/36] lint fix --- .../Settings/RelationshipTypes/components/TableComponents.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx index 11628e8edd..6e7e16cb6b 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { Translate } from 'app/I18N'; -import { CellContext, ColumnDef, createColumnHelper } from '@tanstack/react-table'; +import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { Button, Pill } from 'app/V2/Components/UI'; import { ClientRelationshipType, Template } from 'app/apiResponseTypes'; @@ -64,7 +64,7 @@ const columns = (actions: { edit: Function }) => [ enableSorting: false, meta: { headerClassName: 'w-1/2' }, }), - columnHelper.accessor('key', { + columnHelper.accessor('_id', { header: ActionHeader, cell: EditButton, enableSorting: false, From 4ec9ca9d5f4d16be8b7e71461d44d7764a4c6c70 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Aug 2024 11:49:25 -0300 Subject: [PATCH 11/36] updated menu blocker --- .../Routes/Settings/MenuConfig/MenuConfig.tsx | 89 +++++++++++-------- 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx b/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx index 4dedb28d41..3a62c0ba73 100644 --- a/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx +++ b/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx @@ -1,6 +1,6 @@ /* eslint-disable max-statements */ /* eslint-disable react/jsx-props-no-spreading */ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useRef, useEffect } from 'react'; import { IncomingHttpHeaders } from 'http'; import { LoaderFunction, useLoaderData, useRevalidator, useBlocker } from 'react-router-dom'; import { Row, RowSelectionState } from '@tanstack/react-table'; @@ -26,23 +26,49 @@ type Link = Omit & { const createRowId = () => `tmp_${uniqueID()}`; +const sanitizeIds = (_link: Link): ClientSettingsLinkSchema => { + const { rowId: _deletedRowId, ...link } = { ..._link }; + const sanitizedLink: ClientSettingsLinkSchema = link; + if (link._id?.startsWith('tmp_')) { + delete sanitizedLink._id; + } + if (link.subRows) { + const sublinks = + link.subRows.map(sublink => { + const { _id, rowId: _deletedSubrowId, ...rest } = sublink; + return rest; + }) || []; + sanitizedLink.sublinks = sublinks; + } + delete link.subRows; + return sanitizedLink; +}; + const menuConfigloader = (headers?: IncomingHttpHeaders): LoaderFunction => - async () => - (await SettingsAPI.getLinks(headers)).map(link => { - const tableLinks: Link = { ...link, rowId: link._id! }; + async () => { + const rowIds: string[] = []; + const tableRows = (await SettingsAPI.getLinks(headers)).map(link => { + const linkWithRowId: Link = { ...link, rowId: link._id! }; + rowIds.push(link._id!); if (link.sublinks) { - tableLinks.subRows = link.sublinks.map((sublink, index) => ({ - ...sublink, - rowId: `${link._id}-${index}`, - })); + linkWithRowId.subRows = link.sublinks.map((sublink, index) => { + rowIds.push(`${link._id}-${index}`); + return { + ...sublink, + rowId: `${link._id}-${index}`, + }; + }); } - return tableLinks; + return linkWithRowId; }); + return { links: tableRows, rowIds }; + }; const MenuConfig = () => { - const links = useLoaderData() as Link[]; + const { links, rowIds } = useLoaderData() as { links: Link[]; rowIds: string[] }; const [linkState, setLinkState] = useState(links); + const nextRowIds = useRef(rowIds); const [selectedLinks, setSelectedLinks] = useState({}); const [isSidepanelOpen, setIsSidepanelOpen] = useState(false); const setNotifications = useSetAtom(notificationAtom); @@ -51,34 +77,10 @@ const MenuConfig = () => { const [showModal, setShowModal] = useState(false); const setSettings = useSetAtom(settingsAtom); - const areEqual = isEqual(links, linkState); - - const sanitizeIds = (_link: Link): ClientSettingsLinkSchema => { - const { rowId: _deletedRowId, ...link } = { ..._link }; - const sanitizedLink: ClientSettingsLinkSchema = link; - if (link._id?.startsWith('tmp_')) { - delete sanitizedLink._id; - } - if (link.subRows) { - const sublinks = - link.subRows.map(sublink => { - const { _id, rowId: _deletedSubrowId, ...rest } = sublink; - return rest; - }) || []; - sanitizedLink.sublinks = sublinks; - } - delete link.subRows; - return sanitizedLink; - }; + const areEqual = isEqual(rowIds, nextRowIds.current); const blocker = useBlocker(!areEqual); - useMemo(() => { - if (blocker.state === 'blocked') { - setShowModal(true); - } - }, [blocker, setShowModal]); - const edit = (row: Row) => { const parent = row.getParentRow(); const link = row.original; @@ -126,6 +128,23 @@ const MenuConfig = () => { setIsSidepanelOpen(false); }; + useMemo(() => { + if (blocker.state === 'blocked') { + setShowModal(true); + } + }, [blocker, setShowModal]); + + useEffect(() => { + const updatedRowsIds: string[] = []; + linkState.forEach(link => { + updatedRowsIds.push(link.rowId!); + if (link.subRows) { + link.subRows.forEach(subRow => updatedRowsIds.push(subRow.rowId!)); + } + }); + nextRowIds.current = updatedRowsIds; + }, [linkState]); + return (
Date: Mon, 26 Aug 2024 11:50:06 -0300 Subject: [PATCH 12/36] updated pages e2e --- ... render a list with all pages names #0.png | Bin 60222 -> 48315 bytes cypress/e2e/pages/pages.cy.ts | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/pages/__image_snapshots__/Pages Pages list should render a list with all pages names #0.png b/cypress/e2e/pages/__image_snapshots__/Pages Pages list should render a list with all pages names #0.png index e2c497dcd5ff9711e741e247da9adb07b2577e25..8a31b3865198a4a1ac2d93c4033378842330159c 100644 GIT binary patch literal 48315 zcmce-bySsaw>D~l(jeW^9RkuJNJxty-N*u@yFoy@yFt1^x*McHx_bfA-F0r>pT2vK zbM_wJ7~ekMxBh??&vWOz=e*{1&By@}NKF*ctv4ZY}{{8Wxg;M3emtXQ>1;RZ2b8oo%zng}( zz5R0sIW{cmpI7aF^y2ODXAJKd|IS9(O6S#|3nLKzXH6E)!I-AH#AvabQ{^mue~UL! z(!TAbm2Ma=^rzSE+nyyxR>Y_6%zpmCs|A?GhGPwcR5GG}M|w_0XD>H7xEs%J%bWf5 zdFZdh%?0%)tLQ`X<~{@BM&Ccp%()ldh|oqcwY%5ZuNU=ER zTwGrcS6I3(5b*nb|Nclyd@=&}>L=$!;Vm*Tv3Z^SZpTryF{j%KTVJe8TRRd_+sSdZ zsZUTvVlZqvoX}08<*F~eD)q3T{4iqDy^RW?o5;EkBE+^B4A=(e}}^D z)g0#QODON83Y(3zcrO*ZH&kwX&M$$%Bw=-Ai^X^sK-Bnr<>=@;noFq-%w$9fIy(MP z2My~(|6I2h$5=B0`$l0I$bxjrz z@n!8CLp18oN&YNX^=!9}@;KOXa z1Di>y5(knwG~IDW(;ZnYYhzrJfBZmIPfusEx)Wlfbaxj>xt3L;$3h6ko1n;vo8m+! zh)mwY8 zq`*$kR4t+}lZ8KxnFM6B0@j!*C^Yn-apHx_l_{B-#!#pYcdgSKSb1Rw93Pzn=VJBX zR{epx;5lka$`}{4<^JS1(MrBSx|h9`^jfu8ft_umvXy#AYUaAtvwyo%16^&+)bfqy zgls@GolcRORBjo@^A$3Otmh9-4wmBY8~h}d4PRszye{QTmH{*4I2wq29{Wa%DN|;5 z>n3HQC}FtFk*vyWvGXUfo6ep=pmNOxokTSKAcCv&-@{k^bIH6yf3_qpWp~0KS+*kk z?cEEje1%d1tUzzjFuigG;^=x2PdANwQv(K#I_l_Db#_Epn4i(<%s$TDx>_8hKS^>q zVtjL9n*wjFU?48s%dtUPQp#X3MeNTenI%Uz%SB79O<%NA!ZvVw8jWsPoXx?gv#_vG ztLHx2O*I@STPwifG<;SPiX;2w3m%7)@eXsHjeE*&^Io>_rSHMn87a*s-rnBc$@VRM zXr)%G_42Kcr)6C1U2EZ7Y~edNI$B=#ev;x#-4(L<@OS5q&RJ@@8dxncWNl3X2h7Mw zUWboR1hSfqefSwl6!6DU+}sw<>4D*EuREjv_1n%~e|!2r;*S3hc=-RaNn=q6)?W|K zq4#%H{-?p@zwGLN7N7nXO)A$Su#}g*my)8Sx#Y&hjfeh(Sa(f!;R^^5&lM^Db#x1m zHDrB=KAP(xt)?as6al5xtU5gLS z?FA*Wz+1E24JbKU&((j{H?U@Hf8N~klep@K-lXPCm6@oQ)0-X|5)%K{=mrPQAjovC zhO&zYL*0&6nPycg&z!f$^4lV1E86r=zHO3IYOGeqrnZmnwZ{3gT9hi&KfAb=tCF%* zWjFSV&3O(7Cms~f`4k0(2VE3t#VjD8DX3GIgw&jxl=R#SF4Ev72t&a~8weZGcYN{8 zj+C!U4=c>3<9?z{w(9pf6N8zsFrDrGEDYtRqT@+S#O1$$0bakmZr?52Z5~e{Pa$NJ zRbUnJ|0=S7!2 zFO9~Jbl{Pk!kZb+qmnd*#~Q$pekt)Pd;Sb==>%$ox3 zQswvW-|O~w;NGP8oEjfqWJq6@gK(l(s_fT|AJFa9-AgDTCH1qf&nxWvBQx)LGqMgd zGM{HJ(M!+>Fj@q978hely^r)Hr0PV~T}VMFdjWsah4GT0J2(J?@vWjFR(5weHZvwI zDgbG5bMr-W@PVB|V65FV)Xb02!rfW_`DnqJtX}65J)mZfhK??_e{Zv1^3wGXo)t3C zb@H)!PZ)~yuvuu~90c_P0g{IAnYm1Fj1VfnDEyqAjl{>KQU6+4NG}y`z@$y9@ajoB zGjnadT2D{AS;qUco4An?)>?;67T{W;p)cB_x;RiyiSt`Sn@u*R{NFQbG)XupzsNJ% zyt$fvEEP;#JTL%7Lg<8)TQOX6cgZBJpTr+G%-jon^MTcyDZgi?%o**u^Xke~tEXj@ z$%&j!rKddVcQj$gZxs}L08~uPnqm?Xc+z-$-vT>z(=1wDpcoi9EvcCQulYa)_12In zD72`w99~q@g@$6w#TLJOSkpY!=p%)Kk3^&A0%rOH)dC+&U1(C#_~b(zHt!cSgt=0C4}NO_}{7$<{=1 zK;IsKpuE;8OWz+ov`a*6J}9OEoj(`Q2cvSV5j0i*XL=iEHF)Sl{agq}$|aPXI_pW^ zAcj3BX?+Y90CDews3@gfeekS)^=x-{xA0!^WnfIX#UZ?~@ZR&scTLZp!{|y#HT;~9 zNl5q^q;j06S@|TC&Agk80q?BJW|qoSa5V0X5Gjr*;AVP+E0!3aA520 zh{-RV;1jEN+s0~^-vK*@>5ZlHpaI8E{-m={z4qn1;Wn5TaBwTYUdSmaR;>;WETvG! z^F_g2q+pKLvJTsAb3YVg_=`{0%zl&~jeBj(KVG1?6uyq+m?`ouU#X}C3`tyE`W;kI z_tiZfv1{G8@{M#qUgwlhehp8*?nXvN{&0Zlq&z(C=1fykh$_A;gjJa)ERR%4v#PQe zh1QP;_;oi6?j}TAauO73-jlhQ;D=+aAztXUg`y~;X4*M0$^2um_CALq@$$4Isz>#U1 z2&t(s9Sw?+-d;7UBt3dnN_rnxQDLKr%T4sq-2%=LX*C@yYoLlXHX3&9+k3b9*8>+8 zM63c=NgFA<-|Fiz(ZZcYoSa};`ICIxH=!$81)m|1j|EQpH!iNg8LgNVXq2;XIOW=X z0^5eYFO@1O1DyHW58nkCD~%PF6`+p5jkU#>%iU-s@T&XHJEK?~ov26!M33qrb;$UqH^dzKZyP(TPT@|KXW`oJi zeh(Y1Q$5R^m6dhe`|Ggg8SKU3&oJyuRsezfgNgDrFvFh*V_tUQ_z31jh*%z;9xe~X z-S+6}>a99%UK+q59<-n6E}Hd&$y)Q@dSW}-1D~b2Y{Tu(>-;#jOkd(xsVkkZF!UUg zLH$v^FCjL7@=1v8LWwcK@jUJ{i-q&uixlP#)LD|4tG`R-fxnS&Qm46lf6^%#*TBn1 zqgjZ`kih;5U{rO#rjGG~(q}NRs90E1e(fG$Z^8`*Bf}h!+?#nFrcC2TK8`XcbK1r< zUZbpjto^9cCgjPC`|oiJ3hxDhiAIvl`5Oywo2t=o>PQ~)@;0LSD*egb1E#0k?THzEf*?&%4a~n9nxvI4UT)PX- ze)-XTB&1dX4lAYW(7+c|9BaKX-yMd9l6YOQ+Ld%SNzpPn7E1dU9@)a3eg(kmIcj%d&MXNGkjMoW@z@x_ig*Emcalcpl!LZqop_RoJcd<+w_NfYSys_E*@egg3>$ zG<7w^7)f9uYuS?8H2=fz!93!=9u z67dQMXKo7>URNmNEfjxS5L08%wn%fCLuN9Uy*l6h=}hbrFb^SP;7iu1M`2#_CUS6g ze%LJKqj7L@@EHL_x7j+N=AaE@V){fEZE)V?H75&v@dM5CExgwMa6CGbx%7mA36BoAzBz`g? zDP%c*e$2oli1DRtwp;}_Tl_wNO9z?vtCmow$^yA&GikHz&xdUo-LbL9a@d!r)wfX$ zpAaKQH=znpE-MLf@#UcxaL7xS3mWlv%e;u~ZGoi3y}buO)~ ziLb%y8_ne+FYAzylauRMaVIAQ@QTTxq3PV`*GpA>{cK)$y0| z2rEnSoX4m^MmIK*=e*gf9at$;+f7;#gtd3TQJ`sk# zJ(zQ-Ji3U(*6>1F^_gY0@}I=H_Gu0ir+?i_rk*6no_-1u7BAlg6GB!%3sWd z$XiNpYP8F#@7bR@^?OM`2Ti zWLf0=vqsPq>o<=_SZWM1^;^cD3EvUtZOZWS(a-IJKbcv_zALLNaEMkLZZC15GA``W8+2NycG;~WQ&I#!9@&x(iO6SaKT|BgKldCPW20<5=GQ7xmz0vC z_eQ|_E_R;t`_cp(u^sG-S@7s)rfTxK6wWa>Ln6nz=J864&c*dL3t(idxGfG|1gqJ7 zx9+NvN(E>TK@Qtd|KPpbe626m0RHRi+nzaTLhf@?HnzC4$ z^Ut3@vsm^b#xQ7!=<2>4&sWrKo6?D5R3V7n<#E%SN;J3peen9p&80315eKD}&W_1dGfNWqXT}D(;YkYN?b@BmI=9tT;*&qD8(3U0T=!}6sLEkw)FVV#~WXVSXBX45Dn_PF6t$WF0a^*%5 zNg1@1QHv0NI_z=>kAUqi(zhNA2M@obIegY*;J6vR>^E1yQdVQ2|Cxl;J>1%0#8B0M zs@|T`vZU0?_hm$c{|kh5yS2kZ(km;(6r0>5ZxeU@`>dxzvt;g=Th` zQr2{8qH1b#{MS0HuCM!m{NhV%y1vcc6js4_ZzJR~pfZX^!;>XTB5X7$-nyr^H(fC} zfA05`zkG$^8ME#77}TxQYTOBM{_l?|kzO|*pBd6Bns@B+J0+~-!*_6) z0)dmdd2gnDBd~epl7GmGgNpzei|I1KD6-2!0Awmae(4tITwaPXQFrzn??Yj=9n{0| zff=11Jw$YOB;0qMi}{Yqw8!rIG@QrkcV?t(7Xj}?Hxe<^-DEy-`Qb^$hx+tZpQ34A z_4eSm0(Y2{!NH%~XK<{JP=1t|m|~hyUI}qOy(ZGR;h~_B>kZ6G)k+tpVaiuHUrW_q zL(a-E(5{}X(Ew>9h>UwMi-iksy__m(6f2myD$T}&0AX(Nh7wC_kr!ksM`t|5KOB0j zbXQmMJ`^Knh&B9tXZuAvfm0v0PpNQia$wuS(XHh}dInSJO5f1b<*cS^gFH^ygsaEL*lyxqRPjJsud`CaLH)7j73t9u;O%rzXOJE&lyTXc_E(M)2N+`OupSj0 z3W9!rcPsDJdwD59>j9-FIZMm+ow~~tiU4f=F?=t>u^)^8KNlIC)X=Cm*u>|5qWxH| zirJ)ohva*9<4MEAlPE4PpnccIBrYwPp%_dnMQ{!)0*_6e;~f|?Tq*h7irbMkf@YK- znz(k1%LRGK=ft4JVKK-%QG7wbQGm~&D$qGMJ?daE;|pNdyIPj4IDS)J zeaYC*9lxumn^rlR)ao%t0Wn6cR{I!1;P$H`L!vg`)lY}B9ln<8g65W}n1r8Sl@_%- zIktby?%M2*c!alWF1K?X|4RV4IwS6nvKN4Y9j+O^8EXFg>(?;-&Cb&V4x{&g%ugXA zs=07*)&VH9)#jqxQX-bt11MLq5}3WOw=*t3>+6>qEDHX&w&33b$V;6O8~)_xxRVM* zBBSYV6%}l}yu5M8t)JE1ZX6LnW-f}fz4Abgj_sO%ZH=tH&pq8f!{+Xp4{L^Oph}=taudAMQ{a{V0 z;P*_{(|vc&SmVj!V~ciQ2ltt0gDLfI+aa<`Wq`XUn6CEklx$ibg^@Hovfzs;QEuWWHEmhOSK-5fo}g>Ali!U4~u;KAQmSL(Qp974n+?x0MqCyKm2 zES9iRX4qr3ka`K1Gn7O@PaoCPST_u0F?Gd4)uG>(@-={=`&6Ki38|!YslCBR-o1p8 z2O4i=wp+`Mw^l?#jA#+wr#Q|%B)}{B@y6l3VH(UYK`u8P<=SGW|7O$~;l*b9DFQeO z;XM&=Qu-YpSUIJnC|Z-&C>z4!thbx#SUc?05ksEURQI&4BzuSi>Nwbm^RGDE9xEnP zA(E1h`50|(U>lt}y!e!VeI(0_I+$Glc9_(=Mh>uzk&AfZrRmeYJ2ad ze2*DT8=WyPe@}AM&t=w?X}@3qLRO(_)7PMWDOe<2@6Gbi1-;JLRGll+Sy)PG9FM+g ze0SZG?pBvlZLD1)b8j7-Le=`mv2V6Hj^6iCfe#uuTehX&QV>B8=2!y&|vTOFyx=*Z5 zCq&(~$8vEC1U&Ifu_d|_*O60a$9RA+e>X|Q^xz0bhgYdKr#JKy*oud$3!1*$4;d)` zaKPni;%IV1;Ef`8=N44yPc7=@l7kq~JSIoi7uaB+h4q zNU33mym&ViGIuJI-qSySHH);Ub!q07)n@}W_EX{eSmP5RmlgxrGxrQ`Vfp6i=BYZRs?Uc^z*sp04zZU7czx6im_9KS6Aol?<8<)oN3 zPY97f50E33+4kS@Us=El+?Pe;61xdqT4ni%%!xJnei^PEMHd#^Bco?~HhOt12V$&B zrORW0$I{=`PXJ^>lr!pYw0)-0(M9zuO~cp>@?UTAMB!}rS0%B75roDI+hZqDM8fXw z*O5~{4ymNXLQ^vhQe%xqYWNq6uRIs{QgvEa^r0tLesF~s5JGE+jLvTF(BZ+sclk+v z;ND13C^-tek!6)WuZ?)cF^X#F4HX;C*wKb=b`0adHvjM;B8*{n9f6FDlrd9uWU;ti zoZGmsAZYHE2edygesyGjcLqsP8D6#ro;&3-6;6H08yIn^}eL9-PQDPmkEKD3mwvb-s}>vT6(ydewzDHJUS zZbC~jQnsDKs8n|#!wrV&#p=Dqc`Y?-=I+#M;8u} zi9l$1aDPF>$J`*IMgj7UWvz&VMyU!zelc-iH|~wzOQfywY^QOFldVHG=X2sV=-yt) zz&v5(0Hpm(<;s!O{Q{f$NZ3ke04nN|Y&j*8^5FotRB7qq&V?-p;Y{T^#Od_6(6SKB z!N6*Jw@wKv5mB$FNFVd3eB}`038vM&5K|iESH$4TG=&~rlldxy(HZk*D(MXpr zz`dZU8COg4|qw%UgH;_*=$k+L#QHa?uLIg}BCRWZhEb zgM%M>I9PLt4>BX8P8x|64r>tnTHn(CzE}=&=!1p`mbORzb zcvPEJ~zqOUxKV zHrr~>ldfEP@`_<5s%>%6Yb;8NGW06^OoNu$S?76KIYFVi)r-*xnCot(mRQ*s+m*wR zwfNUYtfcNaLTIo`D@mXAj|I{hd0rrtX;&Y*n_Mv@jv9?W~&P;-^MVW=g}a4lqdLXz+=g&y@O9xs;s@V)J9HaaO}*l zs!XIMKo?qxkhL!fb3vty#=Bz|QIZpu2&$e|Y`F8(a$vdMUb>`b{x(d;MCoJZwltJbDBU{TTWyTB%|fLO5VV_S=9W@e^9ujMnEGo;;{%ETmK zKGWM*5)AFHJ^ri@33uW1zOpse<91|;aJf8s8P0)-T~Lo1>*X7|V!`SVJu^Be)oirabb2}sDuSy>|~`V`BSB2g@AQEkItuP!c^ zhvuyAu66Fhzc}dXoSh8>N=;c=nT5T5j^ORgO@k5s>Gt?a?7-}Fts`Hv$--b2$a;xW z-vZ2oNf*^g0m#*)hQ>y5nI;*th=_)^m5=XfP0$xu-ij3dQK($%;GM6%bBV9S1Ue&6s;GRyM#A z@~_NpTvBz$2MXOcJ_TU+|qmA=4%4v<8Z&TccY`adzSY4+Z z^q*(1bOs)b`mD`$4-5ncto2!3o#0Ac9(Jb)p970v{?`llIuP}4s+_oCEn}JbcO28 zoX?5~wY%nis(zKNXxhhYzg}Xqr3sff5L`2*@DhC_cd_Qh8m$k>kv%Pjj?NHTjm@~Y zVVHM;<%fcFZ+s0&kEv(5S_7}Na$$&N5sck>C=ceWufubbJW0(p9>^Oo*QsKs9v?Twc(NNo8){9$<1oV*(j^qiN5BF?tS))_d>Jm zq1u(IwV!Ztai`|yUo%`4I8Y(v8Q5;`@}ANAAEn0{Q?6@lfw(-~=kdK!(cWHa~ELX*_+rEecB4E?5)T&03N| zu@BzuZbdPE^1QhuGaN62E0}ci1&o!^DlCXOdmX1y7%5v*G3JV{eNZD-+@r0)xRsTc zajf=_wdmudM}MBamQLjLp{-0f+H7FLx!FRvxV*Mn-*L1G2n_6EMg%5&K?b5&Aj}*h zhgG@7FpDPcRBRF`rhAR(l~_7$3TpVmBhjg{!6~~lOXlVs?4M3O9E&vd52$-zUsc5X z(E8Yzf)u;Gy%{AL+0%!LJPNJi&r^PIF1*HgNZXUG5bGSdz_S`aiX`eBh{Z5nehXWA zWd)CnEJS4ThKROh1m@cC=)HJ@t4Z97t~{qJZ3uu?+E(1Hv$KTqu|%I#c+FATVGF^@ zjU435N5xq2?q2Ndd+Pc06#z+vCO?#4xz~8(XJsD>RPES(^}dgeSSP3NZp%AH-q{Od z8WXaj71pwS8T&iaLD0)t+AuD7QVhF2>#ZQlplG&kZ{m=bb24U{{_#&lT>cPHT1E{( z3S+^fYgZ78!17w`)za=RNNY@Hn*zI3t6TJE&orW#v|uAct2uglCVOaD$s%WUOD*Vt zvH(gN0z|avHx7nZ>9vk%YArdFY5-6!Gr!lVv)%REye8(5%TcubSIn1?fM*1R!rG0u zA){8U#6v+~e~^?qDD(5#+?hrg%a_B9%{1--&_+=B?sjBkL`Ge6Yssas#Fh1)`tSfrPc1 z*!6t0ty62b()ziT({A*}=IA!A)z zN>&yHjGQ@|NiBgXrtlTT8%zL(qyV-|SMb*bk?GD4l#@6NlkVJ@G7<9d`bYxlm9gG# ze|2SzuEH%7mn)i4B`CgJfa3LQH~|4IDNy`-G)#qn%khPJtuF@BH=zZxFK`T`W~=tw z6q?kHqySSuPNhYa16-b&Z>R3EBJXq)Qbt#EyWeZ`(eIk3Z9?yu*N{-w&N&HW9x559 ztM8fT>+CvCZbK7utTIrbr20a>sk3*gnb!IZ|gAYZoxMW#XK zM8v{Rp76mTo^^Bz{ED=~Y{%tqg5#*ei58AEDEQTAZ)<1DGgMLSw!F&3y|NAwgzkT& zZd>{#{ybhKjHAPIAo;$1)aza@!&C76R~T$2*@KGqJ+iP}i;s;-t)~i{Rx^U4l)yjM zOit~3N33LRyI&0@EW|962+;ts4L?>$jwT}Es{2D?* zkH>NBCO7^F*y%meUC5!O&iju&y}WWiWx&@u?%#$^Rk>n!$tFRTjLF`=mr9=!(7Igvi5|2Md_2w6u~Tf;c7_TVlcE`Q+(T`Ti?lSyVuFPXwfB zmd-6VNZKNicU6zY7M$6e>0nx`V9a%z>)rKOI*FNtT8#bxf;f!_>?`V%7wF3?P#;c*b*JjCv z8|{(@v)`D6fpAdE3Y@gI{hn2r);kNf5y25#Dy7wAlf_gdnHL97?ddWz1(n0R{XQV{ z&@62~C9>P&GRm9YnTzQjH1&V@x0fuAK@Ao35F(;a!5-*YrJ>hy@=rWGEVZ)GV(CKG z+DO$F`7u3r>C{6N5AJ#CzpN;={{-JVz(j*<2c4%|?Trq=v6n4Gc{EQihoYW>ZF z34acZ6>55ep#S^VnH<1twMHYk{CbNCy-iPj;Si9PN4fSt0?a6No4ViQoofh-i09!u zF9Q-O;M$!(ew2S*TVG#pxZwNXx2t6NB>^>NbZlr1s!)PJ-6Ca(vlp+U3l6sn!6BSK zfEP(BtP7OnoG;KW&((ey;ttttwhXWtDWsN~OY1Lsw14yVIoM3HrwJ1v&fb*oIzBQc z+q|$bKUzmxjgGwvO9>ZLSLe+JPLtVwLP0b*u`Qh`Mn1zypU8Cj8?pO>XCL5L!>B@0 z-uZ@-@sos)WCgGq|C|M|`nBVh6bOj#0X%ol0n}GSQiM=#Sr({vtRHRIY!}w53j$=Y zSyz}K6N}Rv#u+hh|IniAKrm(Vn+Qq`Dwr@RO^GSoJdILO3Kq-|F()lE`%0yyd z>k~5~0!>~Xy;fd@H}gV~D`UIS(DUl4nnNkAX^Tb#{g z0?S=MfV;wY0RNrV&n)x(J#dI4Qe0tIR#&C3sZ{05gyqb{78m9pk`3M?WrM2QvY5FC zx*1gDWl0yT--J^JZ?T>gcdD$iQ@ z8;l-MtO!9hhp<*innI~(cPe=DMXZ(<>GE*4gc?8_4s^}jCxG$KmdvrcQ86;w1QemA zO5F~=Ce8)7T+X>}c`X?!N^yU`RJL?#yY;}KL(W{Df z9_i--j>GC4coY`aa&Wp|b0y2<3ygU__&R`fRqF4YHGj`~xqB^}Hvx`gxIdN0YwjC% ze#@A6#^y*Kp}V50>eYQ0k;~Uf?E?>@yX1I(dQv~c_%lk?;D|Az;HVkr>azuB?C~_i z%F3*~Mlm@I5i~WSu#UmEKYO@(xXyXb9S2>HYB7sG^i1Ag>zbN+cebRh)}T2j5+*8= z-D#LDQfVDihoIh1&Ja#lnCGv!xBl~nl!HVtXUe?+7DoF0FPbyr5)8%Z_b$(2;rx$qq;R#(14|rtk({$- zg?we?Lnqf}E57AjC#!W63=Xpe-L@z$^0`K_ZN-6uBl>}_P4m>hS6265)*A5$6wbUY z*&}ouEmjZAmT83SOxuUe+OMe8>yY7-R*x*NGIunGeiU!YQXu_yf&DR3Y%oNm2Gc&Y z%by0Lth%#TLeh+d^^{jqnu_n8Z%#e_%D(f#0op)2w_KCSqeZv!uN=}D!_4kyF?rc|i%zK1CZ))M-xI6Dw-%uM0dCHXD zJ0Kki4H$n{EU;kM`erOg^`{j_J!6;&)nA)EN+yLZ6YwM*&0#M=hd`0i6%pl&8g#SNdB3+a#XvyHL!Fa4@ z{DJ1OD=$jh7>QBjdj(x?6ljD1{@sy0d_!i=l7et+-^Io3OKZMlPx32Oyr#Ph z-p_!}eL{zspDqyXy`*-CN7k5AzSAdw={aIr{rxNJnPZml!@51(;Q%8+F_xdZ)Qw99 zGrl}3x{#L+24qwL?-P2gJCbV%?|Dd;j>}VtD1qsi{U3LUq)=egU@IML8EUU6E>pm1#iE@45H*XJv$590m};(|w@Uo|Ge_ z#r%!*UCu+a=#@avuXu;JXK*M|I|62z>8USKIAGkBaJAI>uWTE?RaOQV43(8ue$vq^ zSTPlMF?qGM!x};?sq=o3&Svvd#QD2q2aU16lR|Y=n2{FDTcKs_it}sIC*910)1bTz z2*%{Zl3Ut&1oFtVrSV4Mt&aLr0+s3`A~jNCR9A*%DEU+Kz{qk~EqApiz0>8_lX%ph z8Wg|$u@51?$+vl5kDB1$wl;f+W_-5}eR+dVjrMohzjb^6F&A*XpZfpshpWBYu7A%) zsKKvJ*b@8iI9`xw3jeV((77;U)PGzE75bn3@EMrArFn~Jk}(GjnD4)qFC!9`7%bnDU8BWLq+rF??M%_X z!|Tqttw&ekyvE|YiXklwrqjPMv9y{9OC_hk^Q%pfaY+inA~C?>9}I*=N_ND&wx=qy zIAd@+VGvQqSL(f(UV#=an~F=Yp@Ob1I0+eq2AN%wuz>)3T$lUrT{zM{59vzcOlS*v zH{Lyzl=Nl{kydEnJL$JdOMPI?LDQ%+9r4Y2|LG#8|K_4e!GAC1VZhnG474L0gL3F7 z?Rz{+S1g%tBv%cSfAg37Jz*IFah)T2;-hjJYZopn&Umlu2nWLd*prgCIjj#8p&U9( z`<}qv<;x{+uFD+ffSWwn-8Nkv$2;rmsl7!&ye`$~->OYo@}KV1zU_5B`Mn_{I9Z;` zv#qqSsy|WEAQtMjhe6JFU0d$9#&;wU{MX&|_V!ZJ(IuK>phfm{BxZh~{y-BAT;$jn zOreJUN;)I30$Ja!&nqC3(5IT~Gtq2qGc_N>YMfQeDUKdHX8qZv@clR`N}G76vhv^? zc_~VC@W2Oncq*g!qpoR|NZN;29f2L{vN=Ji3^2(h%?!hnBaZQL+nZ5`F6ny$)Mt5q8itM5p;nfVYhU9c~_?4>O zs4I)o(nR|D`qp%NL{;n)QC4(5iUlMbPK_QpLnD5T*4QN@6rcs@b6i~Rb+UB7`q&`*wD@4N*ehs1F~x?@!|P5hWg@YQFq@ zpiDpj&+2+)hzR=>J-eU6YLp#3wvqcW?}@d6%y*&7x0;_PQ?zin>}b2(=RF#e32WiZ zCnI$`)i17&ryIJ*w_gXPvL(vr4C$Jii$#i(NZ|<}I`i<{G0HY?@w&$JrcPfShc>9? zU;)6uYT2tUb8kr$tU7Rgx}b9f9mcS=T>pebz#WoUz4ya)l{V$PXQ`Bv69WQ%yD3pU z>10tXRF@sg(?VUbef?QKTnneH>|e`vHy;I+(>+2rF)g&Tw0m1He!!wy1dDC(9F(Xz zBt;3ig@KBV?a+F3fG#4tPz?6ZkWThzgK@3-4c6y68yJtV8PmS(e)xvE;yyjR=tf2M zPVD{ddAIq;(UGY#(+BPHr^!SE(}m-)T?~CnUuz>ijb;N5apk^7h%@dQx$`zDkxilga9w)_w>E$d=3f zB#cR19YHjD7It=-PUF42GE(V|%2Z&FbS5V%3-6Zcz*8FUhnn7%J`|eEm^}uNNmer} zJF8TG*6bml5YV}6qfjU!3_!g{2ofc`^d2@8esMTdV=XBL;NBhQY1ECVI8hd_be07P z&s}lbnH{fVjHufW5-Wt}RQ|5@RJT`!`slr}2;cSUC!`2?q5wU)x;bh9O!2~4u_!oG zBT+GT{L4{daH9Q7oF&Qc%}fyEiCd}r1r0dU10s*RCKq;R7V#dM9f1QnrjPlV_>Ims zLTMkul&;+`qjQE*N(~p-KtXk`MgiW_D?7cuGrk7+>XlAvm2^%(2IPG9Ux}iZOX`-M z{XJ63FfpMB3LFs1MUJyyE^WL6*%l6;?klm7WN1%hPJ$SZqJpjco;J8J=8UetBwg9ECt;J6jPlRmvHN}LebR=9S@!dvMi-0o+*&6=x=0-!c z$fsY5@3M7b*V%D@ekSVhj2vX6LbXt37FS$ll3WyZpJFt=|ZsU$u^q8zv)sFz_9R!~1J~|lI`zf0+ zu<+EkXRtS0(}EBpjW3oLVHQ_;l}lG#EYidBxzzq%hfNh}zP=(uZ6g$y*^mwFWME)9 zdOPfShGZ9egCFC&uJ1al;4yt{MMXs$6XL;+(s!j7t8*}v(urNf1{pu9%QH@WrQ_Mm zI29FD`_(04^e@8P2)J+XH_{nbwp4?R(8X4u(3bAkqkfI9&$Won*9Q{b18%c)sz#sc z7|TUOeNN-%rkB}r=fhT`lJ7<`w{?im06(Pn8`@0`x97L|^IY{(UW5%OIw< zJan;<3ddxr$f5>MLsHmRLNk%Un zjcAwQXO_9u2KFx+yh;d~@NGPjEtiZEhgJ6sCVCxQf&62E+Hv ztYZgQ^vQ45Z$QY`(KD6~E#AfDyOHm2Q+<=>SdQ}R%Nu1wHar_{Ldr;gpc@UB&Qi@a zT=97C>@pWzn&}B`P3o_YmByJa)`k+f`dY2_!tLztbZ>U_unKF`wB=)J=_pd=!Y;a& zqXvt5c?VZw!h1i-k`VDNzMZbz!0xA2C=i>JF;!uJtLe$=7;YI3TM!JSi9)HOPzOs)hOC(qm9zHXh|0O2q0mVsb{rDP^a~e(Jf+Y&^p$ z5pC06I4-7PY0DrHLlOw4pE3O9_d*uoJhyxQSHX9B>hDEPW>0+<^wiHflXaU*#8t2en%)WAqJB24VF3k0S8;{H$)W2^f!znM% zQ^}wOA+!Zp69jB&yfyamZLjU;trIr0u}8@=zLk}4`kE4aRmQygf|A(+%QqO86z8H- z=DC4+Q(?fvtPD(-pTD|x=aPDVtNpEee*wi-x0B;Ai8H3nm$+6J%U(@P$c1uwH$c8a zPWnBWK>*h1QFBLDu1_l&U+FJZnjUF1u{R{aNi?cbfxx{vvl*#G(T*b=vs0tZ)uM-1 z$37pTQylYlET{bVb2hSB_o~pF0BH5mbWPglXSRlws{CIjBSADR*0=}`Z--rytHZF@ z8#>kr{Q3J7R@K{nZDN;`bb?-QRCX~kE=tOwo(KmKmj1y6ysm7g%k}tfoosblHx^N6 zGLd#?eM-&6o)xLGL8fwh=IRK$0!|{R~U3 zY8UeB*C8VQSge;b$4G_T(;2&+cRNKq4c(`|6A7kH(G5cO-xZAis=dy^2^Q^taF4S1 z)rSMI)?985&s%N1eovV`3MHb_wL5h}*etyv_3K+ zW^jJ63E8;RQ2=q4|J=%e?i@VolNd%Z)mva+ETlIWEOcX!WE z9aBe3G%0g>Wi*?tIRX-=6=?Z@nfTgSm3)0cUC4z6M7M0G%HEynrW75otHbGZbMORS zK)h&1>%>pkKzt7K!*LF}-*ah5@ZO$v`xBx?ik3GWH2u0}9WMbTvRi+qXL(Y+2$%l} z2>`y7^YM{=qv4aM5;e8DitH_+OLcI2a~=| zXBr(}7_G85U0N}Aq3pk^F+(M+M_L+Q71o4qo#FribkG+RFPE-#*-9m`#F9L9wl>_S zRDEME3_Kbav916Ab&KujT3nktTvWMUeHp_PKeVjnz8XkD$2_`?Q~|%=TQqAaT&VIh zJGF`GHBJ119Un6eqOy7|Ru7KPZc6X)DC=z9|)2U z-qi3+hiYrAcrdLoF^(DXf)&Cq~DP@u+ z0j70_xOPx}nKTv7O4w(GwArw**t|Hbs)_#5Ch#i+T}=K5ryCng7W_iNen28n7IFA2^x3_@GYVF!aw}pgs zgNSsubZt;VQbJ0)8w8|dOLun)h=??ZGziiuAkrWW0@7V)KJVW9efM|9_nq^P@t^;U z!9XA2dDdF@y4O9gd0p3>w=h8^yc>{Ldt5y89E#i?FGDQ}@)HC()^y3KSlKdp~0oKBPZn^~bngREhY#1WJ|e_)vV_ z*0bI8koAfcaAjML=HfFT`EsrQAg2mE>TI6~p^YH@O51Q}x#x__`v40vMF2;;l<1G= z%r?8k;?WOseX>ew@Yy;h_6a#&RBTEC8qgoQu!v{cQ2g@sSd-6(q;Tv~4$1-u;B?y0 zTehENdpx-n1SE< z{I+^va8~smC7e>ebWCf!y`u>nqHQ{OxvM-b`PHuz>?xj-g&BdW9eSP6>|5v2G1-gL zr?Saj=a(dax|4YwDG@Ha=lhZeTNA`#05-7UVO^G8Q7I`Bt+IM<)S8@KbPY{+G&PYa z_H0Kdr3I?cR^5c_t2#%%WV8lsrtmq_w}%lL!EgEmTrPrbNY!_3GqhaeS=+z9_Uh{T z`tsKWO&@|IZW7V$OXH7`!S5aV;6`6UXO`J^L+MQ;Ov?|9LTJh*Ri~FY|!L``n z)b!CWcekO25b!R#k<4^rLb4B!zUwLE4H+2`GI_0&lc-z~T7GJU^KRfQY|nLK;IWwr zm;i>FntGb5=(!4WDlbZ&-Zm>@wC8j)EG`XHn$8Va_>05FpP-b=d~?mm2CS4KS#q0Z2zs}DiYRogR$vj5NitmfEMjcD#^-W{!&dR1Yas}0 zK1)Qz?fJ#;C#UXQ#`@%~Us7KJ>Fv{=P&fny_DbNi=s`LuH@{Lv@){01xJL#c{2L=T zAT%p0rg#||LFa9{Gf8HqM3x-WLq^q7t&oH{FEQ;49Uj}+5Q)l;?2mS&k`up)O5WRw z0@yE4!uv+v6|E=X(ClK30?3U437rLr^b9fxNZ5$GZ?Ja$=;DpY>VRIGb?2|kCv(9j z?G+LT@$lz!rt-adw&1Yl-j063as++`oUH=vKXhH%6_Xa84ka|YSZhIy@>G--G>o7b zNRMdU+{hDsR&J=-5qaOgG>Q;m!d3~gS1Y|vnGPqK%%YS)RcY-{kk|*#j4F|;Q^Alk zMFWuL9INKv9yRmd6@NCVpoNttUH#r3 zYCuh(NS@QXWx@WT|ht!eX@~@e5 z&rfSs{;p|11Ng9C9TC^vqcZJ`diN|(%OI5Rz1wU&2M4tX54(XRaVY)>r-|I72eUlm z6NNPn4Cr3(LeN&TUY;%C_Lb-=eEHHFNQq&&hmULgmb6=%H#WwWT5!sL2zjKV^Ubr* zKVmZ9zBAo7ODttooTs$Z^2g8J{@rC3iTkD%6BF@^s=ap49)bC%ZutXy$>%QsJ$8iZt3036#+H%QpeJcLoK)ZbMGnD*uJZq#SvR=LtK zBLZAp*{Tl-&*^0^nQ**QTM};EA6* zBs~@B_3X{Ve2{nC=-$%;o&w!SqiTVPIbP4OsIu2oVfgf+(A03lijKI$Uw}QL@4en zfO&6Eh;48fk5xMuARe|hZ}38iL<*k`YcrXsgt9UxVr9PPeoK;x$+lzG0AkhB+adF^ z;5z#d6|(`Ao_bV*z#?XCoiNJThIN&WuOCf3T1Bj76iBOoz3rD`LG%Z)2AFO!mdCc( zc~2s^nx^J8u55GgV&Asn`)7GrNjaGuPT_=uvcZE(KbIfT4jg1331Z;6ROXlkg-ti5 zhz0Zn)VMPwEZC~m;El$n8$UdfL{ewXQ!bF9*h>SByq`3^#qfaI&hx_hY4D)&nWvs* z^Wq=s#4i-?%g?*n(iA^qY-uV(m7+tp^CNkkFU4SJH#6@R64LEiyChiWWEqwpB*=yf zmko5B!;d7Y3-^QDfMxTY`z?!3=Mk&%;E0xmQbD}XpgCvGh{>Mf5aOlkl5wklib7I% zA$!X21*)7cH`z(Nc0~9mrlxk)iSHq?@A^dC7mRRJB1A00SKcN-D9~@Iprhp}hsYFA z^ZxopGx5>j&Y&Zc?mdbpvqjqbxW%H@fyEK%Y`J3_LlATRAKt!X!egr;rbKAw;PCQg zhxO{OTYUTi&+1K|GvB`-s-KvC;hzq^5(eeBSU%6Eb_hH_nWI$Oc9y?7e-MbdE@c}o zi3=HMIx1mBzCDG9XOF9ajfq5IRGkAI>+9iPG$;jnN#(d^4$E4P%m(!yer&;sXQV+B z)sw0H-AATAscmp9Ege#fj?`D8T%e&|^#z2fuYI>-kLy53;Eaa8uJRE+!dEf)vV;iu zaxWutSY*^SCZitn$I{yGTdB{>pA~+i$3umoEoB6pPdIRY=`)+u$>AExq|27#R_dt6 z{ugz~Rxf>Fy*{8#g1x6;b#)%pUM)N@a{ko<1Q=|3VlEt7ZopjOazBwq*>x_nSw z^;T!qJDC9w6%X|p!}Fzg|M-lywAHO;vF*$ZBEgrmfUv$SKsLe{kJgr>+Kod^mj_Vm zOGgI@#cZHqiB^wV*=k!ko|6ETOrEccADP?iqLh1|sX{{w^PTI##ZGrdb(Xdgn>Zpw_YHhcgmA!o;4Vx6cd~#>WO1c`3K1d)SLA37`#C{|mj)sMuInOc z&*WBcBYr3JA}yCzP?(>rB2}d)?-I@PocK)vdX#n>w=SH7j3g(p5q>TN)(LpcA3v+i z8ybanj`luJ_1+>RG((K01dz)F(DaiAsPPoe%w&X-pQrf6GoG)Q2{MRE0)A-C;kXh- z?=ZZ9t^p{=;rN#qfTmepXtv_^14eQvhZPttf2>l4{9bB@5odv_>tIo%N2z5XgQQ2* z$)#}W{=apjr#+}xOby@45pY;{~q zW&d!4Fp@35K>&He<2>b<#6$&KwbJvH$gS4yry%((Yo=DNvc-;!$@=JbmRu%JOiae6 zrUnK;oNP7bD`D-GSlmBNS!?~SAg%eGPoGZs}~>|Q4n-@GicX4 z`+@S{h7aWVpF{l`*qkG0=LWgciW_aSp^I03K^oL_`Sx3r=#250OV95B=9nLT4j8r- zZm=C2rc4cfCqz@=6aKpC@H3zj%h*XGAWl0agx@1G>2byJ*uda5vKuK?CzMH~kJPl( z5+DG~3Z)1N8T?WT?u|FXpz{!`c3r5{y7FH)9R|lE&qpkDl6Wi%fiI9bo z>Bj5d!cP3_!LRY>L-g+WaXR1W6t1^mtoY%iL>b%L1vgfSs$}&0^DV;(%bIl+LQLPE zzfn$TRV`9Z7OdvvbLUI>Srzt->OJxlk1+M2CJ%lg9%b{87DK_a?o8B#P59@*T`6u* zbo-QJ(Giam*;IfEq+nYDhbGr-QZ=5-0#@Iz`{IeSI8H`pAp#;-0wG)m6a!FwF}QSt z?AG6mnE`CkDQ21M1c#*aleR-%52%I^a5^l7t$p3yF~D{6Uv|31#wwdn^7;SfLtb}G z+zQ+Z+^rN~W*0UWo@f-(UZ{v{&H25kUL@jhW*jS-`vGduJyPxzf_@SN-=@xODDCsz zcD!v>&#Sk8eCqz$A@9%Iye8XMj~lvx2d{NtNfjN?`7x5cxU+xw?w4Q{`~LnRywUHrgeRCeeTX1pR3kL!%28E-lHW=G}!RUHopjv*r&}tiQj$e7^TP$IuhJP zF3V{rtw0wsvjKXY_s0?tGuZ5zyQE_K0QA?OZ5 zi;=oJX`g=OKo)~QDe0fKr#sc>JO_d9(mdOe0YmN6L0d9*v=7fSK*r;6oAiX>uoC4+ zn9O;}JQFK=H|a2^gtW$=p%9 zsEGuk*I{9a7({~b0P*Y}OY4>Q9&p5PndR2J5{$GFJOZbegvGIPGCx0mto^Y_{xzSX zvNEbXX%N$9wi@;Kks0ZrfaaZF8watVtQ`>u5PXms zAsuK7^UuA8O9fxav=KNkoh%AiGVSwE0g9@he3f-){p(9eUJ+S4 z9-D5s_>r?GXy33(w{@@xLVHh)f6cYCgP(mLszi5T>8yo^DXd9*U}#RkaQA8RueNr& zuXaD6aG_BFj*l2ni%J5@D`LoW-zgG*VGD{h+%+hCXB|A=ceq9hoym45R%H>zE8-0{ z+oLX&{N0~p9zSg$Uuer>P#O-cSl*hz+x-LZ9stJOi6RZTKccc&K7C?*Ao@+Ez>>le ziQk@$B5pYIJD-3>mz@*wM7qG0ze;D8{m&Cz5JB-=Fs-JAi!_fHbqq`LD>)WK*bMDT zR~AlHOLP<#ZJtp<-qc0p~xIz=M&SAFIY*j{%7Y+JIk0s^a$_LE?sONOj zC+9qLhw?R!{lndbdOHGkw=>W0C3DgdKNBn_-`}{4N&ZDHcW4JHY>9a-2w)C$m_Jgk zSL1FvHDnbJZqTgaiB;J8;ueFnJ!UdlbSJ&po_Neq;?#SB5_qmQ>s?>sd&C?uJY??B zNTmls1%2CiDkIa;2gKEOlji58^?l8HZz}E&>hCOak?sq5-V2);=~Q_iEu7maPshKE za^p(GbHDw5XdNGyUCS7-Fz+;!2s3#d%zdCBh0xz;JbbkZ-L89Gbb!n8*XsDV2A8L_=%j!3=OXJj;AbJ_B+VOBZp|qIAVY1GM?U-CB2V_;Z*-WpX8h7Eq z!Z9QI7pKAwXEpLnO;?^g2r|OzoMMq~aK-&V9@N?1g*#kCoQ9N$A%wexAmIwe;^O1` z_7THv?x&A{Pi|yWRV$$Q$z@dr-^F||zp>aJHRr8>uoI{8TH*KwM&xU1NXaDH=XxYL z-{*J62_sMR85l?50(%8+qpl)gZ=NFEz__?url3q2ZkYdxo>Oo)$1+BDGRH8t&!r)N3LoWGg?+zedq9aI9V^xE z2_JkvSH_J{S?zFoc3LjGU46#v&KXQmxcix!`l(|8y9v>vWfmZwp@%<$(|k z)2w2|Cl=mQafjMO2M5$_ibEnnOLkZc23ScLRax6cdkel5Y;2*KwYXJ{k?1HV9Ov0ykh6&i@L|g{~1mC~Qd?GLEs3|Z> z<~pSPa;CRD;|@&BMwE``naTYrUeUJZBEsuO2rs6e74NTS6|PDjwa|TnbnPC_Ur}B3 z#!Ci6wmVcd(V2dJ`5QPw_dY^lAIv>wmdpSBn4hBBnY0Pd$BQ zdiyT@mTs)N0cQ$3Q3Whg$tiNQAI2=CT{XBm!ij=iy_82dyA(IKtg#jb{cmR&Uhk4z zup#Jm@Pc;zZTJ<##c>L~?6is|wA|~;E)!PF%9~%<{5-e&h&zHZU=w@c6#`#i=$vs` zO={e{@ZxvAaNz-cZN7m4nPT>Sgd`N}-*Qz!xN#pRUG=5%B>v{37fE)V0bGqHpB?JI zJifs`3gGk&zSfy5)gD|au1|U7O@twV3M%Iz#C(L!7ppW${01%VsjoQLB;U|RrIUqW zk-8AZzA0-Y19!zV2#J~R-o+$lzE1K$@pskvAAPvUO7?fo&Jp1m)QaAWn%jWIjK6;? z2U;``YaweCNP7-m!}al3zYS*QDFd0=^=fWmbtA&H5pqr-H1=-6e8v)g-%SP1DmWH+ zZ=>z6u~Smq9gIx!+F^ure<93N$OdY;S|+i-bZAQ5Uu{bRiGwY^M1E|i_wYZj8|p|# z8RML`8Ql!viR&vcO5uOSywE>v{>|8IqZ{)P3AZ?M_6j$AS`ERg)~ffof0`+S8b@iZ>&MyG6A(pCICzPZ$sshAcVoB8mBJvw`Vh0{cDpfN}><3qFD82uj`}>Lrd?+Z7ttry$+RV=4;MK3>}&1fByNK zA$o)rvOCv^{ylAYu<7(SPWps~c*$J-6Fx=SsL1SxM52_bTXeH1 z&c?&z*n%NPg8$f?$NR}6bn*0pvCko6V%(>qe;act zbd|txi-)>4wTz?oT}C;}#K5YswL?w9JN86F@!NzMLQ((Wt-d~4h<7NSep26aGMV%2 z_c_+2vI@1>GiJ0GFYtLjG0rBobt2^qh%BCz;{_fxiYMvPxQTuX8Iv$L!~FMv2=(-E zmVRVF#i%N{Wb64#A49?ZOrz^s(tpq_X~Jj{ya*XRkV%2 zEQe&Q^25Kp%~kk(_)0&tpNR33gQMFmHpRji+@so!f4g@ZSpM(s-O{+fT;~2*lu@=c z$tRb-F)~-E7ee5YR?|27(~&LhjXF3ick!`PdP_z|NQT}O|LzfWh5lc@+!x@>o%~<( z<^C{j2=Nc1oqVJmlQp5iR93zsig)7CQfd}XB>&^Na?4z~S*MZ0eUH{iztOJ$B~LD# zvb3vtj_q@rJN+J`@oh6)FV9QRQkzM}=lKXSl#jj_O8N8SgSO ztTEoDul4-W)?1|aDk}3*Rw?>mb9LY>;kb+GuUi!C$;5JNn2SOMb3*b;iIGObqUAg4 zCIq7ExcHminRu=Nn-9-Ep*)LDD@+A?N1s5t7P;ua?Lp*&l{Z`OLvVZX{9e7bgSydEmi;6$uF<3`c8et<` z#Q&Ol!PkkYH&T%HGURT(HT|Oz9jY{lx!vO7eZ@5%059iPe)=XPBu*Qf{&Y)CmE6pz zcTHe@tyR2WlP2PUjV5v($U%j~X=v_~nFOZO(+`J;md!D;T>v8y z7*>YsrRc6oJ{~yz{ALv?Q?L|;dw#;?tx@%Qd9HY6JliHY@2!x*@JBbOwh5MN%!GD3 z8~_JEsyeMlq`7H!>JH}HlBhZ06H#t9uu@~zk$$%Iv%w-;e)4P4`rUgZUvViilM=<6 z5)yA5@gP%Nnf&F+wJxInxfu*YmQ-Fbto&^PTkr+x1K>}ypj|r`@RzGwP}2ta$SzN9 zJhLpM%FHCZ9~AR3$PGAr=5aoJ$5@t!jUaFj{pQ5rQ$ur`FBQO67JarIGY5&|Uq;x7 zi3I67^! zc6OzT`JC-ZL7zZe{uA5DfjRD19XFv#!ik`-<#RJoN-CU}ahz>*!ZgnZ zF1vbq7PhsjSP^Lw1N{n{w)p5|wg#wooc6yfdU`dM+cRh;26q)Vpm@1E=Xmc3BJY|V zXfC}tV+N$ymGMFVjHkPu1^G+7GJEvP7K{N~TPO9X6H|Ljnk1)?^T&eHz4*1XdAA|K zijrY*>#4Ug2*;^B81q?J55A`Z<%caE+-wNK{A86yfL1YW^F)ms-Rm1ox6jTEKY=~C z&dK`AU3A~wW2P@=j_Yu$;QZ3eg{TtM5D%|e|(~)*m zQ^-VJa6dvYZ)y}+T%T7cT!}EBr1vkLI^JfV}VcFvJ1IRv$u1xDfX6>!$YbBE1t@)Hd_lO_g~>kDvuZ} zURlRx?uZ)w`lx~M?*hJ!exm{an6jZ;)4`EfB#x(l0Naa&tTLGUnBU`!!7eOSsHg86 zz`Nz`ii+XblmfbDuv}BaGxXfbd*SkjOBv!Zg*K6{)u2F{3 zc68(F{QZslTsQ&@Aw@=Ln#2^?u`HUexJx$9d_<40UtT`&I$uaXw~w4YK0lrQ;@d77 z7rTeWTg`dXl`$bID-VhJjG19p zE^UT1-Xr6WfenZ%1%;A_ZL3@_S6Fa*4VN`8_iy^X>+j4rA~lsy`{POB=z?19jaz4j z#+&cAyL%g7l4`kg8%-{85Ix%c`6L(V6`)rk?&-$0pHrE;xj_$WJ!xKlW^jZ)g{bYD zTgXu1c;kk!+vOQLEB;3Ls}V+_G+uE|hfTZzonUCuiA>F=_fF-Vub{yTpFAk2TdHz8 zCHWK}G5l7^D<|i8_va|7R<-p&&gn=Qy>aByd@h|_ibxjFYq=wq1uFGO`(}zA1x^gq zrMiu#ZOIRh$b@`rQ;6rhHU{6ZOc4tkvO_m7F^5x`o3B+??01LpLno%NzgFM9b<>?P zt{dz2{78fQVd{*>k>kZPYNVsc1Y)eOVNk_qzi5gB%BdVCiR{$(OzDbB$K-l_;whgg ziOF6kZJqQK0gFe7t_}|=83REc*jwAhwNxz?&qy7ez-|h&B8;LKvaIUUyWmRW*?y>! zn{B)GvoC`mC4V@t%gRxj=xWA`qpR?)oo&F(6;Zkq6$b5OpVomRk$n!)|?Y@*K|!;-xt`xnh`;asB0Jso3&l zx(mnbqUADqqO#PB>6+h!5 z?E*uNZN(%D4fvnfU6)KluLo~jyHC~{VjGG5G1^VBzmE`zY+_)(^KP76I$mg(rIpKU zhq81IF0Nd8Cd_vV4u4Jh9p6W^{xnYo?<{Flh$;Jy z#dDnOf!w8g)&Kc9ZnmwFa^prLA8b*;YfB4BVDG`3@{cxKA=H9`LA7X!<(JhuZyV)0 zN83{h;v`3kp5D4gnPZsD<46(pKr~zOknvlAW(L`OeU0-j5l*pM??t<4rNur5CpQUf z5i>BI5@*k-gF3%GQWSuu)J%iraUp`9%10S8?uwCFQc#ff1~ z>e9KEw0rcc%wNWl(?fgNx#mSft0F?@jbx4VjhR=CPzj$eO!T0VzM!I>sus$Tzf1aI ztm21kG}VP{B5j)Pl}<<`{}a&jkf2*yTT9D><2r5*`qdS2Fz8PZ*WUG8OZLNaDa+6F zVA%@`4>cI?dKyl`6aD46)N_qdEs0<4t*wj3afw@ZC@wYYJkh11I)|IH zL^0zQV|tgF^*FoWC9OB{H^rS#pI|})(+jN^A)ldmwTPS z#MWIJrYHzw#$MyW25N5UNP9ln>5Ssj_Iee?pY>x4T~`@$ne4urj!6~5)`S6QFP;I7 z8JQ6*0kmw$gimten63Tx)h%i|qeXUZj%=|;fri2UwyBz0KF9e3v7A=F5Mjs2Hx~Qs zJNw&%`-B!SZ%y?McpMxZ*Lm%!5$eXW-1Hr9o!VqFistBKr=Yt9QS3EYv7s*cncrMW z+hZ;TQ-lm|SFW&{c#N~&7CUh=5Oh&#)G~qRinbJp!ZPPkoRZG0&-D?6Ugwyr-ZIeK zmy4i`N+|TXXg}SN`6GFf(J#&0$3h-gH@hR+sL#-eUQ6vuZib=e`7~$VwnL{Xg&nqV z%dOY|br~xz5rD4heRT`^dA0wLG{iv}!(IQDjgHim(SR~b(y=Az0^SdUqYD$f%IZ7= zzdE9~Z{Oyqk>cV%kSiqU*JyI$dAZ734nwuRUYgA5b;IxOK{oizx|SXK`un20jirZ2 z^fyxCQLY{!FV;_RAO87xk|? zT63^O$R_p6^t(~z$)EPBgSmm+JDk*#T< z9PQ)S%qv*F2$X7--7HlL!;KaDD16_s>HmB?bYB$jDV*^-8GHN&ubsi0QOk)sVc*Od zuWlwrchLv^q~&Y^@nUm_rn^yvJC-8}zr0262hS>W9u*UF+cLUr=RF(f4**H8g3AFD zX^7)4!!LbB;bX0(q_{Z0LEgE&BON3U%Eubj?kIFdgV2lEdM~wkjm~rsnVL~3c{BaK zWK>${(n^9#IN70aWS-0pJAbCa){8jxYws%|xkxG2q)N|jW`&EUFb7*!)netCj2I90 z;7ESymv=pW){ap&vx;m52Zy%&_|=G4qTQ%KnB~0WB1(h5tsq>mts6#l?!xKTiVQXA zJx66ac&yf9TUw)h&Xfcpx}|ic*9IlJ^(`PZMW=^HeEHS}R`8!Y6rGBh23sF!w4+2{ zyO6R$gXV$DcvWf@Q)YcBSG+>+RK?99^VCNlf_&z--HtBq#FB*)BRqg(UsZ70@Ym?7k8u6(!zF*mFP`Qj^2we9TiGH$ZMKks10&D zDD+?Y93i`#KnDS0XoK%=N0Lbf?AhLf zJtYl~K52(Hhl*$aUd$tzqx^xL6Y~7>a%`CZoD{i0Et_y{*3QA|TbSg+^K#P?)I%Pa z%24T*o^ijsw9pRvttj*R>~h9-&79l6dYNOH5V}-tc!=~twcz(3;|4W#d_&nfOzuZJ zCXQ=%(haG+D{H$N)P#$rph;Rxf6Z8Hyc=_0)GbgFeTIXFXTeJ*-u}mw$P7CVPmX() z40JB=kG2u)?)&y;Y(rDXhoregTMqikFu=OqEHYDz=aT^KnKMN^6kL}cK<$|#MCk(M z+M7&C{StY~24(-#i6Hy#touH0U7P4ny104fod412DKlycE*>9_hsO)}MfAbmwz#S* zJM?gCFIPp8#31|K^j)Cr?pQPe@074AFej5efxxjoUV?w?mb<|YAc5~t;g#d2V)5+_ zD}GPy?qVx3^^C#_EU@=@g8YZdy{u4E(hS%QHlJwT|We^M7B(cg4p&LZP}Xc9&S>N zCRcDc_+G?rzk4%eJX8`vIG_*-y9Ffbsk3%WbjM)ULPWwD7=6jAmt;hzSZRBRv*f@Z zyTOaYpgiuIsM}A!sGSJH}hjzyoGIzNQ9;87|wVnQv9l(TTAgkQi;a^k~V#(;~W zL8o5Ot)`8m3#WFHx(y8tp}n?9RtNeF!nJb4%fo(skDI!qLy{g}#+Z}XvWml@HoXBn z4e&pl1m%F37}Ss~ySs)*|!s@A8H$UZso1 z$+R66ezaCkq?Gt(Y`RpY|IvKxt2Bs=5w@D$4`vNiv zJ^0c!nO!|5`(12wbV2?bA&3VjAAf`WtFehvfg%6BPy!Y?T~kxj2CwtT<`2s44yyFq z9gsNIj1{zinn`q^V>gQ^xP{kZZ6%Ee1GrAT(b~dD<}v8^BrjJN8RoUg{L-hjq0lSZ z)$Nf%M>@^hyu3=f6)bS-=UOg@UJwxX|G43WC`P_Kb6y?%0IiH+(!CXO4NfcM0{!83 zfAV0#IN$KFZqDe>=q2M-`_%zqYJ`XXcc`t8+r5VEA|q<{cjN6#BE=MJS)tuMtTGme z_~@`FJXvkk-3j8w2UW(w3_+I|Xgd|#-m8X--xvj<@3n3efNIjGU`yq;Qk9^MGk`sT zIBHVuZ$SZF*7a}3U0oZqmv+glnQP83&-B(TJ9nx=avtVEUdK_W1sFIU_2((O+Y|)jB50~ z=if>+6FwCSTg+q&KIkVIf)qf&wYqh<0-0R-e>hOOU7fQbg!Iv$u3v`>8Q@)z`4du6 z;Ul$B7@W9lg#Vc$KGj`WG1YjXX{c}fRyuS$q^_pOId)L7Fx z!H=}Ne#U~tl-R%AQ$=cQYDxd1;0BUFBLrrxp3E{TwYH-1GBhrLyf3R{&aljILK|8ZwKXcBQ#xoAmWb*c=YaI!nt}D}MHdu2j4V}AQED^6ALJD6QLrp`Y zQYrZy7Bxgl>sN@v`*q#vU4D9KiGq+?Voe~fkYG!lc8RX3<}@L*i4 z+cgn(PweX2mPcxk4wB?v#;Z;+u%)CrOHNd``}|$GW8X>Glve)(k~~3!`>jDe-{ORD z-xaaYK<5Xm97Lk+#Du&m%H+lzj@eAGnZpm&j`$I};qFq6mqtqt?A6bneN%2vdh_Ij zuvjhZt$GwIvrbbW7=v)`;{=-Rcktm=E-vITEt%Mugh-79KG~e?1=KgKol&4&AauHZ z_%Nv#1@*oJAmy&!>FMz1I9SkOr@ha-Is&n&XUJKRHYd@n8B>+UrzA)R8{O@)>YDYI zDCEOUHfXewJ7yge-kU>CxXT(z!Wbk`CmwL`6Ml*QbqJGh;gbZj8AK19w! zbmDjVwG>ehg5raCeyI{ryT?BhBlf<1RhXm2A@+%XG`!km19)sR>=coGmRz?xU5!}nu+K5~dX6o)t^_4VKS>I|R z_iJoq;d&STMIRE71vb4!Q$w6Wp9z0zLAs#nX>A!kb+h)0+K0EJQN!cROQ+-g>>5Ae{K(Cfwt`Bqb|APK zE}QR^IaEAg(&&xLALMg7;X_-In^)0%p}8>hkpjE^$N};MUUf4_!J;JN+RGnf6OB)5 zIWAA4%Oo-n4A^XRl_oc`C}xT*ZtMSdvw}pPpc6v^F4rpJt?cR%vu0D^jZ1fj-Qnlo zOSD>pKH5$BhAalfL6gfT$`Cn#NOQPYvN-CvPiej&jW9^pJmsp5Np3q#t2Un>y4^j0 zIgiN62?zpCq9Hbft1?m>-7*7g@%($Su8d|ir?KUuc!z?J*V?R76iNG1t<}X}IwnIK zz#@Tl~k$UF*yZ%N;*lmod$3r=^ zP8%=Cm`GJ192+Q>oB8YKkAob(24RLyee*A9EW7{(W(#3aJbYMi$DYwT)bFJBPb zam~+j30ssN)WpZCd>x-8<}lDAz9*BY?8wHC`?P$)5>&aXU&e0Ft}5&a zdux^l145OaZR-8}Cfv~iRy?TW%BgwGL=BO5?eIc$xwrNuER6SM6A&?W$h_hi{SpM# zF^fl<{#(=Xu4@9bQu!lBP+Ir+V)?fFsfz?`i}Vuda9bWy8|m4`-iK`Jgl6Cdf2=z( z!A}(bipg(jw(;(_Vx3!l@bDW#;Dfz6n&;>GlNuZx9KUWHA}9f17$H`IrCY@GdqW;n zhzDNpkbRN5`|?cV5loceX3vPs%fK?TRZ~b-e$RQ<74trxk@{9pczaI;edRm(&v3GD zlo>fRK04tOvg*!vy|xdJh+sGW{_)1D!WLEOXfskJY?MstZ2kHB_f!Z{4$G6T0x>Y& z07AZZF7n%)?TsRh#@7IKyP2-`xZRIPTQf7#7?fIXgosl}L4p=tGzWXnnJ7V=di(OR z3;1Q^PAwyl(dPOw{hCZRj*wG%K14m{5EQxFTtY_7DHOfr!QixO5%i>(2LbLIdXgNM z=f8A9B?9zZj%-P*-hNh~dO^%d>?2JZ7n7wo7I}~Of%IM@Ufs#Z$3UY6f`B6aN8

7;;OS|CFL;Ee<6XMlL*YpF^}>&Xf~P}9BwJ^szo2vLu^ zRIrcW+Cp$Jq|;@R7QCDUgpuHuwCO3lN%B#r4#FTedv-kwur6?ErvBcZ1N6&*jt@%- z85w?J@Dxh_n(_F3W|Es7!T%Ek^kH8SOR&h7edK9*6P$;FUpUV7GV$)Iu#A`dvB>E9 zOf$i%p^66QDp7NwvqW|@1@bkX)Ls6uSf`~WaSl>qNyaNoT<&Y-`ilE$Ih^<8a zBw`#W*70Iy35z}3;aFvDi%U$5Y?~cOgkG308F?_E8HLC@sEP%oYPwp|=(Ii^iFs=9 zwfe1)v(yKd1eUw!qF+b|38cA@j+T42m$vCONkZ~WtC$h=BrYMKDuJd+WL7R! zAj#`4h9gK4enIjNp(BVNIg*!bkDn?MI+(O^oJ208_K%wVB=S@eOY8}HA9ctIg}7> znW$|`OrCWuhZ6}R2v=D5B)8QbjW>y zxs4qkSxv59qoWNc3r$I3K!m665i<~FNF2Gi{tS$5V4yamlY&eoT)6=80)7LgtPxXI z!%?<0AXQ~H*Bz(jlrNqOxa_Swo~+u1-3Tq*4@q0s zxmIiAJfmKMm$9Xq*C#R0L`j)kWMw+Q6}+czofpnp#uz{J}MDe^EE5I13QWlObqCLK|*~n-MS?S!m+mZ6%dau@3*x~(+vUniUW|1-9c#mt^4T6<7Jnuda8C!;g zc}Zw*GA$VDy`=cXO+z*s@1$D!YVmO_4j1y=RWc?Auet;G6$>x!>@E#G_7&##cU=uF zg6uRsI=JKHuwahVCETsHo67@QNu+HIf-fiMsl~DYFrqT$hR&X2rF6OvH9 z>!s!+!+ucHNWsCyP2+kwcpItzosEGwm9F~s9LB0luAc9v@bLO$IV2c5wu-tjn@ZEs6w6r-N z6!7QTFkxbl3x~9TQbe!XmA;E>w#RvizuZ$-W_c=PT~rJkmED11EUPM~ZH}-?;<8zx zbU(U$4dbp9d0a=$=w>kk1`{pZAW3;0vVN2gc)=a}+2u*sm4xp?^6) zbnCe52fAFP$rscsX;7G9*Dev+#A4H)@H+^O)?+&{oSe)50-%Jy-HXm|DQN3UV@cE6 zEU+6Rjo~>GTtRd=F-J1|1YFwl6Z?-K7!NdP#_jBx%fVWKkbDz$Hqps&;mv1nB=cBd_rGxN=<5r2wrL;|_QXPx zPkRJ~pu_JH?jp?=3#Wj-2Mfh1)&KGPi`8_`5sj)t&Z6fm;U#mHkM9Bv+K5lGV64F+ z$?k|RE|6U^N=zz&kygRV3{sHR(st-up|A~S`!|MTTBxdsO+P2gXI+2BkS$KkFGF)} zXvFY1|5m`wKDjs1#N%2$^ud{;y#}paPwsuQ`U_wQu4F=T4tdn@^+us zx#)@^UtdS3+t4v)B>`u^O)xxr$2MFsU6J1`QXgUo}#|Bzz@!U_Uqm+j+9 zbNeo}kfI2?V?wv8-+K~XEgNrYehSz!?Dn;|Achr&^5Pn&gwA2J&EkIO#6CYXY>ygb zn3tqP?4bd@5`bTR^H8QHrmJM;yvE~L1SFCZyO)3-VN=AubmG_0_@SqJywVzNsy+73 z2-ejD2mUoDJZT4h--j23%6lP2p#&?oJye;^()rP~48Svc%0cf+yIIrdn1XDlZbQ#v z_hV8SX9bLn@637j256|M0kY~i*!W-Ik^A~yOy}x_Tq?V70TfcE933>O zO&JjCFV^8N-guDjz87oC%(oI;RMYEyPY}EGzRa201jG))>Y#6C6p|4^ z^Vg?5^dwO+dPe%1HtlA68aSDmB zUcFqz16-r4=)8o`kA(c5^14EUEyFVH`LR@gotpUTT-85vg*6C~RJiGvE*9||*q{5o zGN3oPi^}i2(~MXCM^fqO&%8#Y-d4^lX<}ZLSe&_C?%G%TWF;o@r>Zi49{aW!eOaH} z=RWv18UR7sy(V|NS>NZ_SI=tqOy7!hEm*}m=sm6tUGZduiSX}HAyjpWaa<3~ES+g5 z6=1CEIi;F8=iBL3#5_s8fL74L?t(i7keL4chE~0Y(3T6IxN~shM2k!~KUeG(Kj&4l z42{u2+prv2R@<2VaPo`W3T%GbEp%~zUm?8R2Vr=KJ#vW57}>4jcUF+HxJ#TD+I>=) zT3wbS7Dp*Zob*JD$D)r#NT}HSr$P7Xo8&XmzlIeq(7*Y8@SQH>vSWXYfSVPLzvumy z069!-!O;FAbmgoyexkzM(6sx2hptOFOWt)EvzHxnhmShUHg&guQ+*v$;LEn$zJH%p z&-3wSx0B?(28HeI(2>60*g*+kyZ*j9&m2Hj@n!1K(QjA3>i{!xXkJ18OfEdz+<;h3 z;zt?}s`14ud0`fd@sIS`X%Dc8kK$kOa=E4?en~R4$ZM@CVpwKO#dR#=ak!I}0JHP& z8*_4RiSzvu<1OP0-e-&^Cq5gWeQy2Z9y~-Qr;z&hAA=`~>K_9RkCgwv=NDoiqN4vX zs=hne4u2m7@d+a0k^i|I;I=S`OiW92Ol!YO0B(< znwEBzQZ__l8*!kQ4%PZ#&tfGxvG>ktd+`q$nne|3lXc{ogJ4pWZE2alA0}EDdNN9~ z_^c-K4C=G#zZ%1`Ib(PE8UFFHicaQpXas|evdyn)c6KXs!wVS_)fiD@;i;phX`(u8 z3OvdrB;pzhSz%#5Wy09$y?fxmWl6EelVKf86lH~g%g=^!W4YZu&`x<*5u7SZmR0aY z@38znA_6Qe$gM%sO$lPe>MBjnB_{pS4KI^KW8;(m^#fX1`D$Nr`6<%;{owfDq*IBD zP%B)N9kP67{%8WZwexLb7h<6u8-Q=UAMamv!8hh&RZn^N)*#nAuflM8&%v=uNYSGm ztSXSX?Q>WfQK7y_HhTV%9dR{(-Sn$!0I~uZk!}*F zDmj@*rSD(3NrV<4`Iy~YjOR6lLf(CdKAsj_tE?vAbzuh{D*mOzTSYd}n`cMAvjTjQ z&nS;Z^lzziG?aYrxmATbIh$3wEJsN(ts< zaC^Gx)VgP^6ni3e3hN~pTyU%b-uEqoV~C&&wxc80v_m2#26Hvh_wN1N<+vs|Vii>MxT@-3)ZQVu={)X0$5A>$*XoVDI^WT6Huy9Z=oG$Vy zXwIm0ZR(s95~{YGyuuR_Xds%NDO{)jwOp1wFfu(DhEjA3NOq975M+<<%2vG#C(6)Kqw!{-JrIom9(r0D z1?i!$Z_~^z*u-&l`{-y!kTTbBZK#;*8U>j7!RVGtb}nuShqFF6=CA}Rpj>XxtE@W- zprV2v`4IU!{M>~b0RY6Otx!q$v*<4mUoiLepSa+2@-C02x!~c!!~l{Xq2y>}Gb4Pi zU$<|a?B?r%l7;tw^>&s~RjysX-&<@Dl$I1hKu|iQ1t|e(k&y0??o?Vr1f)b3ok}+d z2okdB23d4>cb&QRv-k77=Zx`wIpZC#UzB^XmiM~uD`x!Wf7&~k4<)@Zm#f9kRgv~o z&yDtqkwvc(>gx?dj8AAnEH(KcP502GfqGxQ_6sAB5D`Pr8?d#bBi(Y1#GUDymryfI zMb#>us{mqD4CS^SV4UDqx>0H)0Wp(pYA&>|$6D^s`2cy_;03vvrr?+_f^LX94d+G* zh&@^0f&l^B{_mGcMeL(gvmNRD@T3qr^nl4li{-jH4A^d;d5Mo z$nYKCE(U`cYW|g`twmizm%Vk{IE9Ic$tj;lRj%68gB@7sO6`f^XagC3WlD`1yUW$) zu&nY|3%~nEu~!??sH(Vk1}D)iulWx?GmEv-f#iGh@Nl4441hM^N8lR_e0@nv%^nH8 zn$fvaNRqKZw53x!um#l~0y)+=PLE3&w6v0W4^Rex5QAGS50P{r4~!dTN51ft;z00; zW-8oGwcyZLQITSLUd11HYK}Lr6Hn0)2?U0C3XJ(8AY7}Mtgg=mie?ZZ_z0Tj!NDM>veruXjsAkCR5_JZ+_QT z$aL2j3$@^g>FNA0kf}l6f!aY+>E9zP>58per5ydN?wcIDV^IjXNHs0L7$*51L`d=C z<9GMC4;phLY|TbL<=AxJay{l49KC$=O7~uXq@zQl+;r*emia4Y-JXaj@cN{oQIA@g z0U>}zRE11h?|P`O+RQFKWYVk+N=SIj?%k|ss^(jWXY|p1Loko26Ig3eh?W-=Jwl=b zFWQ|17^Ui=qOd2cRTwMM|PI>*Anv z8~C8-N(;CU$F`x0)J`R{f#`bIczdO7&7HNG*}nwwZyOsCdpIT0l&@Tz>6Iv z_5A4sMF{C0##M9x7C4J9b+^NSiGHKu#%6weS>}bzM>K>K$ST|jF=XY|>C3xooxZKb zJWghmklK`Npgt(j+3HG5<8SQ9#S(r;ZTh=H@CYhth%ah|tQVTqAZ|^S-B-Yg=pbcv zbG;Wa=7tT543d`GLLG>j0Ai>dFb%WpPVLg!Uq2y#KKJ>>QeR&fIMWbzG$ZCR!-XUy zY{wy9PTJm=B(|wy@@Z}ZbPWnX$+0!$02Z;_OQM6M7(hw|g3hEAFGz7I?sS}v2=lw| z#_rXRZkqi5xEW|N--Vq5^c#r6G<>>?Sz*1!Hg9BPzuq7H5Klti!os(4ypT96E@k_O{1)nLWu{a8NgO6!dx!)4 z@vy4K7R)MMhc#&^Sb5@Lz#Tuxv@p5R0(AjA5v!|YReKb^@PrM9#lgRnUHl-q9rWLz z&T^g*TdN@Uih=|29>B)LAZBun1r3A4^j!DTJ+r8X zfTMkfw7}2bFQqFeye@5UY3;5FeK~oUEt%1-`s2eUy~-I!;&5sYgb8piYA1G9k1%Pt zp>-d(&gH*wTZT_?8sL8C4EYW~{|jJ7e02#(b%eJX01Tn8-mU6+xvvEyz~(vq2pC@jD}(EUD$%;gV zq~_2|!4EPLI#AG|q9SIBDxkOusn7d#d;tlgL&dfu{Mh7zvK_~d3rZd#;Sm|VNI+*> zM$Si>Xz*HYWtxOH$i$c4ZjHUsmks^=e2x#A{|lP!LM}kj2FI`U&h~RqJ4RbC*D=FH zpwi@jXcC5!R6$}bP=GvuX33H5d3odrkbGC^WE0Z0#y*4X7Cz*!?Mb88a+PL5{{U3m z4A38+o}{l)Z_timf5*uwnP#;mQAnAqTit5@=RPU%$VJli_EKXE{+R&6kLH z62sJVJ~paa=1dC+e@Gll=y#~#X(&l51Y$$vu!_@cQnq{d$iTp8TQC{)9>=o?UaoNH zH_XO?y>{D|IX%{g)vztz=2(>XY9U>3IAfBHoiswQ(+JD~lys-)_NpnDe!zIzo4y^a zRl88|mL4ySDnBTjNeTFKJCScuY0Dzf>MDb~n-W}jnx|6iXjpX8=YGFoyn>-Qr z*xEA<_!^9t>W`E1P2+CaMOpCa@<8r;pkbgO;p6I<)Ab42@y1ml8B0qh=voJh2Y4w~ zmsSHdh(piX-o05#R{A^kyUIn$+y5Ku95#I0eNIQe2(kPk(=}T~3Vs@XJsb#J*DS&*08D9$ zVm1U1R`AJi#k(wPXNkJY`O?Xyfdu#h=F;%!C>l*li}50Ym1e41#{_gdi|yhcZ0ww` zgX;WehxRl8a@MO~y$39bBpj5)#NC)aM9f-6mNP1Z9r03-2gsK145cLMya)5uc$!HE zj>(V@YZ^+wP*^?=q~!oE1^%%4>71BXyEcMn7YLg&O|@f7ceaN)usz@#h^`Jlm7k=S z;)`yqv3&8nY=2tWg3RU3SS{YQ%$$D9?jVu*d6@(b5})yN&Foj@g!;5H#{Cu9KzXO8 zzhd83hg1OY8%j`72`CHg#KXvBpg%Uc-u77?&biV` z+w*Bd8d}=ri+q76?=~toDW9Ald)5wFRvX91CCoF0hZ2`nMpI{a=3TFpdR+eaAnqgM z+&52}68Y?Y?FV`#Qh7XF(d|*XtH%_hwHFf$2hQ#n7Xti}S&3V2LWz$MLNH)lk-Vma z)qU)$Mb?C1pI4Wz^e*;h3xJWM+@JS?@pBXZhaQt8jTlkHG72%U;qev;gu`RX+P20| z;r-XK?^m_z)0D~S$hlt4>8d;ZY`4Hvptr=ILTM#g9NQAdW5`cXbKb0|MSM*5T$2y? z4n@PS+2sBZEd3dYL~Cj~35n0*-1F6%OId;!+vdM%OYUDRb7E5#$;UOfyE|-mrl_QCZW!XMt1eOXG?v~Td# zx0+F=-_9nfJ7*D1>rqoaCU`{9;`jP={a~dMpP4d_-qiFQ%4DK8F_wMf#W>J4nxjHf zs7lOsjRJhC^~~rk1Q|;EZFU;j%j1J1{^MBEmna963G&c_X+bTHg&Ip+8$+qt_zu}} zU~zDCszgvh>>Dy(`5$2?dC9!dYNFPG)bIvn1}^%COqppFUm~V$kw}YAF+WRQ75A9j zbCJ1q6SJ)%j=+3nT15;_ukhk+-;0f*%~M4p*u)&k0ghI3g}j!QpQO|!EAJ$qOh z-TQTdg)NLCv!LL5ag&IsGkp#$h)Wn}H#QG`%4QDf8kefK^1FJkM)EvTt0Lix9FZC0 zRX|DR5A~(Ea7?U8Wgg+Z7(P1FVym5K?%X(tsNJB;)9L_`@%ZCCGdntdepI2Iqa!E} zDygdn$!4aeU<9x|e8{R$Y{|Zgd@~2d?;!Inwi+Ui!Occ;YnYth*_WhVSQ&G-rE|0` z^0z`bStoxpyS=y}3(0K}{$V&PrDQuzc^xd;D4}+;KVKtQDbrKzWvc7#9iqoGeO2`GZ(@CMSvQp9Par&z| zT>}aMSzX;bM!|l8oAL}&7Ku-1S}3Oa^Xs6o>wsvXkqCum8C!J>0^ zaZwD1-)d@lFw7Zh+HTi%?o%FJCjqnmeGpblr&(zc)tdg`H_ymXml9zCNB8n;QStGN z>4S=ZCy3AY%-|Ce>XxYnUdIsww&$5nlF*;$p2JJKysI!0aT0PLJ=g*sNWaoIW{c0( zpbPS|%A4JDvC=EQ2v~#VR=E?iCv|FOEI68WIVZkS1JZVAP)dvxTO~x-G6Dbbv z=IUqeH{J|8i#NP;uB5mZ9!0-KOU&;2?#Z@l@us?i-hV}HD{_Jo{s@vHm1?Mq4z z`rR6%^d)X)FIzQ^w)_}iRI!zfw%(Pbx<%oTUeqd}pQ4YF8xD=fmvTp7L>Dg&~SUFLmrMeH&gyS}`JOM;!H1}J<$|ruh zJZzv+BS6W`{fbt`B^0_yB`Me^u0{b+^0n_R zMU^|Q=e)#BdRG0U%=27m*n(?Xqx>m|sX5)adDCFWe*pEXE{B=OtFAFomxZL;jKEUa zlCi~rjoR1Q`E=(DXKBAb5)^xF`3Qrr_-KWh$sjpAd7NVh+6d*@*+Ot5-!D&v>1NX3 zg!^#hdZQW&cTeEr=9YZyJ~{*n(s*ai&sGVUHN}K=?|2~G?o`7&JcLf$Gae@v!vp{| z)1KNF=4I|xp{J)Shet=2N6r6 zq$(}>r(%g#CnTUo4QIu#wb;jm7F?t`un*a`BN6HiGTfDJ|lw1#ogP+#G*OO6PN^~Q2bS!81znE;~c93wz7&<#`U>t=I< zAy_Ms%fjzO#eKJ5Woegl@zsrcC* zFi0qH@ZVXf58Y-`lkfQA9HB;XE~ka?lOU?k}y{v<)k8!6QN4k;UO$ekc7Z6HctUc@JW0XkZk&k#TJva*P)VJm{; zX=ZvO2J}3I4NFxE*@<_3{jS=rIuk-0ub=C#!c>-dOeh(V*N-5%KRw1ty4cNy{ZjA5 zIYQ#akAWt?!c?tU71%TnQdE>LFLY`?&FoVUpub)1-EbeGm|iMlzZD*-e)q;C^2*Ff zWX}2eYKO=3l*YqN$mjO-lnz_m^U?27FIHaJvCqwMtv(`TyoPPGZLyK5;BmIbiyuRAkB_nEXtX33%$TouI3Tp1Fhz5wswuO2ev#zP7r}`#f5HEW=^hGNkaXJ{P_bk6%<$< zzm99RG?&O7+OD{+mZ)Eef-vVx6_w`AS$tlhk!VaJW>&pj(ZPJp=80Cl!rsh_;0iZo z-KJg_Ku*tZj@BOgKTdS7Un!<#<8t6lKv+np`rnezh4bI_Kyum$+18d#KhtvN8!+&+ z^#AW6WPz0-uK*SVr?-!@L$AeW4$Hu5Hv-&gy}SFpPX@n9Q&TZTYxJ5_;9+Bni#+qr z&QehZV|9bN!j-c`{V_viYX~&rrCT2S06aKl!H5NHqsmKeUZO9{W$~w{I4!Kb1kdeB zP2&aYnUZoqee-oA)4?haC==M&+r!qG{qj2<<>?N8Q_~zrP@Z@2*Jx6g6Jq%q4&KS@j9bzH^R(ue4~DcSU&3AGl?SR8RPvlmu*n8X?>JEBLLc zbRWIec)RwcLyDKae;E%1W1*J= z_6KYLvET=IuwnyMb9jrlTx-uAiJogFHZrmCe~Q4x!z5&0=$=Y_q+S(qFl5zWQI-^B z+8YS!e=IJ)U(3#YnP1SxW;HzK*GnP2Gr199);hZJG0G#zG_r-wX&<7yl>xUXJ=-Tt z4+HT%&mPBc&&$mH-dlNndWH+aqEilIa zxpg~s4WSdtUM>%0er8vJ{YbU@)*Scl#t`01184dN4``vWL1*$$3AGaY2OIelz9SaG z1F$8-^JKGLl-xHwZAa{4DbjYNr1a!t>5ut$JaV^tkZCJhtiM3K1XDkvuNS`N&Rd)xou)5Jd{$Yj!u;u5N3Y;A&>A(P04uyS+{8&lH6 z9VxKEq$`qDR8?;L?q}7LZgSRoZQ+-oLVHFiQ3U}J5qSU(zAb<8Uk1ApJUoc4GC*oTuRemH#G>aDh^9oA zxiAt26Zi;sRUM4qcj>=eWxpYzYI2L0wrpxLSq5KBII6#tTCC~8BNuZ{kaqnsF%h0Q|?d2iKPiAiK*XE&$fkF_j)A>3NB(eJaHkZ6|)dpu>H z#`EPgHU2U4+jDbsqTDVa{-tfiLb|jGk;f@LPc4Eo+{;`ZGj(=er=q|% zA58DbG{!B*9m?V9GwN9Qkt_q=m9z2DCA2}O&f#@6KLqisujcC1#zsqE%vJp|)e74= zhMd67+h8aqw)QO)S)S37aJ%|8I@+M0)T)u37s0DnM9t~43ufn>DQ5mV`_n_4Tl>>{ zN7C?pn!w4ZPVQS;{LK?Swj?Ca$ADWMyC~j{Kp>~nzp54(TzRa)VR*VYmG)*ls7Wu~ zF;|kSwhq)B2n(0T9G>v;h2JJ>OM4}ef<%I-!Afu}n=XfEt>EF&@n*xv1!8X9tB>{L zx1FF3&wWbI-G@1ggydOS>ZN~R8@c}+lZ@lQ%vI6d{iU0gAx9xGW3=Y{W?heroc!+_ zi#HuCVbyj&x}~p}nMzO6{J}juEfXP1fHxL6(x;^M-JSq~I7x=V86lJcUrIVO3RS-m zW4R9291ymq(srl22{nkAce57Wx`tzzLVq(SEsy*m1Cws6#@9Gjw?EI%J^6)_aBkQX zqNLy$XPj_J+{4AK=W%g1=?F6R^QzgCx_#tD4kGzX==8?0`-FJqk}bjyDZT34Guf zTpQ328#G46PGJ#$X&?A{MAKMKV`#x9g0j*YcU=~kMi#bnJx)E2d@&cb9;!QN<#WCBUE zxsn{K?Jz6CWT`&a{v?31E&7Y@O7ujw8lUe4N9dY%#kCf>bMd!{+~TS4_;k56o;b0F zebyatQH_-`A|neKej{_@OBB&!x8KhUg9^QObqz|@UG#fw5gQYjI5_Gw3uG9Q__Ws#eStdhB4{ljKR>=r#V?!4&OiLd!hs)HDi= zt-U>?rG^pIV>o_&eC||-nkH_FY&&-%j<5I#huy}>X-L6kz#}r2@U4hDjKwO;|@e?~FzJ+@4-Jq9K!MjsXpq|SSw$eWN&cg(6 zB0`BLGmh1m3jDxEoo|(q{pQ}PyZ;RqER&To)-n7YYn2Zn2y!F^FC;#`aqKt@RK8W z*9}2Q5>=zlNlna~#SGd=Q|0W&*%bJ{zGXk$b_sF?IjtoyQODhNuo3E9k>*hl;YRTH>stjqD-NHc)UvR4UCGg( z%$(F`y0x>_*wupfw-#hgk7=n%=}icXfQ(TtVS-99sHOfb6v;rI+VkVgh-3NAbeOa2 zQl;$((r4WkaYc?p+!csIn)r142+NO0llKRYgOgK)* zz?6nhzLxqYObPh6$Z2?iBwdjl{#H^Oua=DHC-!nY!X?rD_xCAi)LMcAvz8ibdjEBI zzF8&qya06&QZb}v{qFqI!MT&v1}>@!xv?c(PBc6Cr~Hzgr$+a4K42b5frj0`)I2LN z1x~IO=sjn`shX3vT^iwjJGwN&{FXr4*5%_jRa<{84~&XeFw%byfoAs#XNc4--R%ks z3S~TT<8WS1EU~GwUDsQwismdXueor%Ur88;v%%Fi0i}P-ovtgca3MI%m+Bh})%ZG26aP0yw^^*on0wB_k)s6V^1I+h@}-L1dtR!j1DMxatj_l~Ry2TR(9EUI@A!YbtmMm99<5v+1RDx|9q& z1&7y*1KfXzwP{NQ{PX3vh`N_JcGoF?cwr{UKHE6|6PUSwUV}-A@$bQfZqiQ+M=bvR ztwm5F9Y)U{GBQ5<_a*QokVyGd%`~e literal 60222 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 diff --git a/cypress/e2e/pages/pages.cy.ts b/cypress/e2e/pages/pages.cy.ts index c4e0df7bd8..fc87bf4b5b 100644 --- a/cypress/e2e/pages/pages.cy.ts +++ b/cypress/e2e/pages/pages.cy.ts @@ -211,7 +211,7 @@ describe('Pages', () => { cy.contains('a', 'Settings').click(); cy.contains('a', 'Pages').click(); cy.contains('Country page'); - cy.get('[data-testid="settings-content"] [data-testid="table"]').toMatchImageSnapshot({ + cy.get('table').toMatchImageSnapshot({ disableTimersAndAnimations: true, threshold: 0.08, }); @@ -232,6 +232,7 @@ describe('Pages', () => { cy.contains('Deleted successfully'); cy.contains('Country page'); cy.contains('Page with error').should('not.exist'); + cy.contains('button', 'Dismiss').click(); }); it('should not delete a page used as entity view', () => { From 56e0a7f9a9feb1a8745605b5a2fa6ca73f5bb7f9 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 26 Aug 2024 13:07:56 -0300 Subject: [PATCH 13/36] refactor + more tests --- .../Routes/Settings/MenuConfig/MenuConfig.tsx | 44 +++++-------------- .../MenuConfig/components/MenuForm.tsx | 2 +- .../MenuConfig/components/TableComponents.tsx | 2 +- .../V2/Routes/Settings/MenuConfig/shared.ts | 30 +++++++++++++ cypress/e2e/settings/menu.cy.ts | 29 ++++++++++++ 5 files changed, 73 insertions(+), 34 deletions(-) create mode 100644 app/react/V2/Routes/Settings/MenuConfig/shared.ts diff --git a/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx b/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx index 3a62c0ba73..ee540a045f 100644 --- a/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx +++ b/app/react/V2/Routes/Settings/MenuConfig/MenuConfig.tsx @@ -1,6 +1,6 @@ /* eslint-disable max-statements */ /* eslint-disable react/jsx-props-no-spreading */ -import React, { useState, useMemo, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { IncomingHttpHeaders } from 'http'; import { LoaderFunction, useLoaderData, useRevalidator, useBlocker } from 'react-router-dom'; import { Row, RowSelectionState } from '@tanstack/react-table'; @@ -9,40 +9,13 @@ import { isEqual } from 'lodash'; import { Translate } from 'app/I18N'; import * as SettingsAPI from 'app/V2/api/settings'; import { ConfirmNavigationModal } from 'app/V2/Components/Forms'; -import { ClientSettingsLinkSchema, ClientSublink } from 'app/apiResponseTypes'; import { notificationAtom } from 'app/V2/atoms'; import { settingsAtom } from 'app/V2/atoms/settingsAtom'; import { Button, Table, Sidepanel } from 'app/V2/Components/UI'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; -import uniqueID from 'shared/uniqueID'; import { MenuForm } from './components/MenuForm'; - import { columns } from './components/TableComponents'; - -type Link = Omit & { - rowId?: string; - subRows?: (ClientSublink & { rowId?: string })[]; -}; - -const createRowId = () => `tmp_${uniqueID()}`; - -const sanitizeIds = (_link: Link): ClientSettingsLinkSchema => { - const { rowId: _deletedRowId, ...link } = { ..._link }; - const sanitizedLink: ClientSettingsLinkSchema = link; - if (link._id?.startsWith('tmp_')) { - delete sanitizedLink._id; - } - if (link.subRows) { - const sublinks = - link.subRows.map(sublink => { - const { _id, rowId: _deletedSubrowId, ...rest } = sublink; - return rest; - }) || []; - sanitizedLink.sublinks = sublinks; - } - delete link.subRows; - return sanitizedLink; -}; +import { Link, sanitizeIds } from './shared'; const menuConfigloader = (headers?: IncomingHttpHeaders): LoaderFunction => @@ -128,12 +101,19 @@ const MenuConfig = () => { setIsSidepanelOpen(false); }; - useMemo(() => { + useEffect(() => { if (blocker.state === 'blocked') { setShowModal(true); } }, [blocker, setShowModal]); + useEffect(() => { + nextRowIds.current = rowIds; + setLinkState(links); + //rowIds is derived from links, no need to add links to the deps + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [rowIds]); + useEffect(() => { const updatedRowsIds: string[] = []; linkState.forEach(link => { @@ -143,7 +123,7 @@ const MenuConfig = () => { } }); nextRowIds.current = updatedRowsIds; - }, [linkState]); + }, [rowIds, linkState]); return (

{ }; export type { Link }; -export { MenuConfig, menuConfigloader, createRowId }; +export { MenuConfig, menuConfigloader }; diff --git a/app/react/V2/Routes/Settings/MenuConfig/components/MenuForm.tsx b/app/react/V2/Routes/Settings/MenuConfig/components/MenuForm.tsx index 82bbfb0917..294e515d8c 100644 --- a/app/react/V2/Routes/Settings/MenuConfig/components/MenuForm.tsx +++ b/app/react/V2/Routes/Settings/MenuConfig/components/MenuForm.tsx @@ -5,7 +5,7 @@ import { InputField, Select, OptionSchema } from 'app/V2/Components/Forms'; import { useForm } from 'react-hook-form'; import { Button, Card } from 'app/V2/Components/UI'; import { CheckCircleIcon } from '@heroicons/react/24/outline'; -import { createRowId, Link } from '../MenuConfig'; +import { createRowId, Link } from '../shared'; interface MenuFormProps { closePanel: () => void; diff --git a/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx b/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx index cbf849acf9..d2c9b23714 100644 --- a/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/MenuConfig/components/TableComponents.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { Translate } from 'app/I18N'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { Button } from 'app/V2/Components/UI'; -import { Link } from '../MenuConfig'; +import { Link } from '../shared'; const EditButton = ({ cell, column }: CellContext) => (
{ + setFilters(rows); + setSelectedFilters(selectedRows); }} columns={createColumns(setShowSidepanel)} - data={filters || []} - title={Filters} + data={filters} + header={ + + Filters + + } /> - - {selectedFilters.length ? ( + {Object.keys(selectedFilters).length ? ( @@ -175,7 +189,6 @@ const FiltersTable = () => { Add group -
diff --git a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx index 092d038559..5d447a27a4 100644 --- a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx @@ -1,62 +1,46 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; -import { ClientSettingsFilterSchema } from 'app/apiResponseTypes'; import { useSetAtom } from 'jotai'; import { Translate } from 'app/I18N'; -import { Button, EmbededButton } from 'V2/Components/UI'; +import { Button } from 'V2/Components/UI'; import { sidepanelAtom } from './sidepanelAtom'; +import { Filter } from './helpers'; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); const TitleHeader = () => Label; const ActionHeader = () => Action; -const Filters = ({ row, getValue }: CellContext) => ( +const Filters = ({ getValue }: CellContext) => (
- - {getValue()} - - {row.getCanExpand() && ( - : } - onClick={() => row.toggleExpanded()} - color="indigo" - className="bg-indigo-200 rounded-md border-none drop-shadow-none" - > - Group - - )} + {getValue()}
); -const ActionCell = ({ cell, row }: CellContext) => { +const ActionCell = ({ cell, row }: CellContext) => { const action = cell.column.columnDef.meta?.action; const setAtom = useSetAtom(sidepanelAtom); - if (!cell.getIsAggregated()) { - return undefined; + if (row.originalSubRows) { + return ( + + ); } - return ( - - ); + return undefined; }; const createColumns = (setSidepanel: React.Dispatch>) => [ diff --git a/app/react/V2/Routes/Settings/Filters/components/helpers.ts b/app/react/V2/Routes/Settings/Filters/components/helpers.ts index 4b6128debd..10425c86e8 100644 --- a/app/react/V2/Routes/Settings/Filters/components/helpers.ts +++ b/app/react/V2/Routes/Settings/Filters/components/helpers.ts @@ -1,20 +1,27 @@ import { ClientSettingsFilterSchema } from 'app/apiResponseTypes'; import { ClientTemplateSchema } from 'app/istore'; +type Filter = ClientSettingsFilterSchema & { + rowId: string; + subRows?: { + rowId: string; + _id?: string; + id?: string; + name?: string; + }[]; +}; + type LoaderData = { - filters: ClientSettingsFilterSchema[] | undefined; + filters: Filter[] | undefined; templates: ClientTemplateSchema[]; }; -const filterAvailableTemplates = ( - templates: ClientTemplateSchema[], - filters?: ClientSettingsFilterSchema[] -) => { +const filterAvailableTemplates = (templates: ClientTemplateSchema[], filters?: Filter[]) => { const usedTemplatesIds: string[] = []; filters?.forEach(filter => { - if (filter.items) { - filter.items.forEach(item => { + if (filter.subRows) { + filter.subRows.forEach(item => { usedTemplatesIds.push(item.id!); }); } @@ -30,19 +37,16 @@ const filterAvailableTemplates = ( const createNewFilters = ( selectedTemplatesIds: string[], templates?: ClientTemplateSchema[] -): ClientSettingsFilterSchema[] => { +): Filter[] => { const newFilters = selectedTemplatesIds.map(templateId => { const template = templates?.find(templ => templ._id === templateId); - return { id: templateId, name: template?.name }; + return { id: templateId, name: template?.name, rowId: templateId }; }); return newFilters; }; -const updateFilters = ( - newFilter: ClientSettingsFilterSchema, - filters?: ClientSettingsFilterSchema[] -) => { +const updateFilters = (newFilter: Filter, filters?: Filter[]) => { let isNewFilter = true; const updatedFilters = filters?.map(filter => { @@ -60,65 +64,69 @@ const updateFilters = ( return updatedFilters; }; -const deleteFilters = ( - originalFilters?: ClientSettingsFilterSchema[], - filtersToRemove?: (string | undefined)[] -) => { +const deleteFilters = (originalFilters?: Filter[], filtersToRemove?: (string | undefined)[]) => { if (!filtersToRemove) { return originalFilters; } - return originalFilters - ?.map(filter => { - if (filtersToRemove.includes(filter.id!)) { - return {}; - } + const updatedFilters: Filter[] = []; - if (filter.items) { - const nestedFilters = filter.items.filter(item => !filtersToRemove.includes(item.id!)); - return { ...filter, items: nestedFilters }; + originalFilters?.forEach(filter => { + const updatedFilter = { ...filter }; + if (!filtersToRemove.includes(filter.rowId)) { + if (filter.subRows) { + const subRows = filter.subRows.filter(item => !filtersToRemove.includes(item.rowId)); + updatedFilter.subRows = subRows; } + updatedFilters.push(updatedFilter); + } + }); - return { ...filter }; - }) - .filter(filter => { - if (!filter.id) { - return false; - } - if (filter.items && filter.items.length === 0) { - return false; - } - return true; - }); + return updatedFilters; }; -const sanitizeFilters = (filters?: ClientSettingsFilterSchema[]) => { - const sanitizedFilters = filters?.map(filter => { - const sanitizedFilter = { ...filter }; +const sanitizeFilters = (filters?: Filter[]) => { + const sanitizedFilters: ClientSettingsFilterSchema[] = []; - if (filter.items) { - sanitizedFilter.items = filter.items.map( - (item: { id?: string; label?: string; _id?: string }) => { - const sanitizedItem = { ...item }; - if (sanitizedItem._id) { - delete sanitizedItem._id; - } - return sanitizedItem; - } - ); + filters?.forEach(filter => { + const { rowId, subRows, ...sanitizedFilter } = { ...filter }; + + if (subRows && subRows.length === 0) { + return; + } + + if (subRows) { + sanitizedFilter.items = subRows.map(item => { + const { rowId: itemRowId, _id, ...sanitizedItem } = { ...item }; + return sanitizedItem; + }); } - return sanitizedFilter; + sanitizedFilters.push(sanitizedFilter); }); return sanitizedFilters; }; -export type { LoaderData }; +const formatFilters = (filters: ClientSettingsFilterSchema[]): Filter[] => + filters?.map(filter => { + const tableFilter: Filter = { + ...filter, + rowId: filter._id!, + }; + if (filter.items) { + const subRows = filter.items.map(item => ({ ...item, rowId: item.id! })); + tableFilter.subRows = subRows; + } + return tableFilter; + }); + +export type { LoaderData, Filter }; export { filterAvailableTemplates, createNewFilters, updateFilters, deleteFilters, sanitizeFilters, + formatFilters, }; diff --git a/app/react/V2/Routes/Settings/Filters/components/index.ts b/app/react/V2/Routes/Settings/Filters/components/index.ts index 02f0cbae75..488fc51946 100644 --- a/app/react/V2/Routes/Settings/Filters/components/index.ts +++ b/app/react/V2/Routes/Settings/Filters/components/index.ts @@ -7,6 +7,7 @@ export { createNewFilters, deleteFilters, sanitizeFilters, + formatFilters, } from './helpers'; export { sidepanelAtom } from './sidepanelAtom'; -export type { LoaderData } from './helpers'; +export type { LoaderData, Filter } from './helpers'; diff --git a/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts b/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts index e1a2e8539a..4893768b2f 100644 --- a/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts +++ b/app/react/V2/Routes/Settings/Filters/components/sidepanelAtom.ts @@ -1,6 +1,6 @@ import { atom } from 'jotai'; -import { ClientSettingsFilterSchema } from 'app/apiResponseTypes'; +import { Filter } from './helpers'; -const sidepanelAtom = atom({} as ClientSettingsFilterSchema | undefined); +const sidepanelAtom = atom({} as Filter | undefined); export { sidepanelAtom }; diff --git a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts index 85e5fbf772..7e52756043 100644 --- a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts +++ b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts @@ -3,6 +3,7 @@ import { deleteFilters, updateFilters, sanitizeFilters, + Filter, } from '../helpers'; const templates = [ @@ -28,18 +29,21 @@ const templates = [ }, ]; -const filters = [ +const filters: Filter[] = [ { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id2', + rowId: 'template_id2', name: 'Template 2', }, { id: 'template_id3', + rowId: 'template_id3', name: 'Template 3', }, ], @@ -47,15 +51,18 @@ const filters = [ { id: 'template_id1', _id: '2', + rowId: '2', name: 'Template 1', }, { id: 'randomGroupId2', _id: '4', + rowId: '4', name: 'Group 2', - items: [ + subRows: [ { id: 'template_id5', + rowId: 'template_id5', name: 'Template 5', }, ], @@ -63,6 +70,7 @@ const filters = [ { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, ]; @@ -73,7 +81,9 @@ describe('Filters helpers', () => { let result = filterAvailableTemplates(templates); expect(result).toEqual(templates); - result = filterAvailableTemplates(templates, [{ id: 'id2', name: 'Template 2' }]); + result = filterAvailableTemplates(templates, [ + { id: 'id2', name: 'Template 2', rowId: 'id2' }, + ]); expect(result).toEqual([ { _id: 'id1', @@ -95,14 +105,16 @@ describe('Filters helpers', () => { result = filterAvailableTemplates(templates, [ { id: 'someRandomId', + rowId: 'someRandomId', name: 'A group', - items: [ - { id: 'id1', name: 'Template 1' }, - { id: 'id4', name: 'Template 4' }, + subRows: [ + { rowId: 'id1', id: 'id1', name: 'Template 1' }, + { rowId: 'id4', id: 'id4', name: 'Template 4' }, ], }, { id: 'id2', + rowId: 'id2', name: 'Template 2', }, ]); @@ -118,19 +130,22 @@ describe('Filters helpers', () => { }); describe('deleteFilters', () => { - it('should remove from the filters list based on id', () => { + it('should remove from the filters list based selected rows', () => { let result = deleteFilters(filters); expect(result).toEqual(filters); - result = deleteFilters(filters, ['template_id1', 'template_id3']); + result = deleteFilters(filters, ['2', 'template_id3']); + expect(result).toEqual([ { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id2', + rowId: 'template_id2', name: 'Template 2', }, ], @@ -138,9 +153,11 @@ describe('Filters helpers', () => { { id: 'randomGroupId2', _id: '4', + rowId: '4', name: 'Group 2', - items: [ + subRows: [ { + rowId: 'template_id5', id: 'template_id5', name: 'Template 5', }, @@ -149,6 +166,7 @@ describe('Filters helpers', () => { { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, ]); @@ -156,32 +174,33 @@ describe('Filters helpers', () => { }); describe('sanitizeFilters', () => { - it('should remove _id from group items', () => { - let result = sanitizeFilters(filters); - expect(result).toEqual(filters); - - result = sanitizeFilters([ + it('should remove _id from root items moved into groups, rowIds and transform subrows into items', () => { + const result = sanitizeFilters([ { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id2', name: 'Template 2', //this _id comes when users drang and drop a root filter inside a group //@ts-ignore - _id: 'erroneus id', + _id: 'rootItemId', + rowId: 'rootItemId', }, { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, { id: 'template_id1', - _id: '1', + _id: '2', + rowId: '2', name: 'Template 1', }, ]); @@ -204,7 +223,33 @@ describe('Filters helpers', () => { }, { id: 'template_id1', + _id: '2', + name: 'Template 1', + }, + ]); + }); + + it('should remove empty groups', () => { + const result = sanitizeFilters([ + { + id: 'randomGroupId', _id: '1', + rowId: '1', + name: 'Group 1', + subRows: [], + }, + { + id: 'template_id1', + _id: '2', + rowId: '2', + name: 'Template 1', + }, + ]); + + expect(result).toEqual([ + { + id: 'template_id1', + _id: '2', name: 'Template 1', }, ]); @@ -217,6 +262,7 @@ describe('Filters helpers', () => { { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, filters @@ -225,22 +271,25 @@ describe('Filters helpers', () => { result = updateFilters({ id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ rowId: 'new', name: 'new', id: 'new' }], }); expect(result).toEqual([ { id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ name: 'new', id: 'new', rowId: 'new' }], }, ]); result = updateFilters( { id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ name: 'new', id: 'new', rowId: 'new' }], }, filters ); @@ -248,8 +297,9 @@ describe('Filters helpers', () => { ...filters, { id: 'a new filter group id', + rowId: 'a new filter group id', name: 'A new group', - items: [{ name: 'new', id: 'new' }], + subRows: [{ name: 'new', id: 'new', rowId: 'new' }], }, ]); @@ -258,14 +308,17 @@ describe('Filters helpers', () => { id: 'randomGroupId2', _id: '4', name: 'Group 2', - items: [ + rowId: '4', + subRows: [ { id: 'template_id5', name: 'Template 5', + rowId: 'template_id5', }, { id: 'template_id6', name: 'Template 6', + rowId: 'template_id6', }, ], }, @@ -276,14 +329,17 @@ describe('Filters helpers', () => { id: 'randomGroupId', _id: '1', name: 'Group 1', - items: [ + rowId: '1', + subRows: [ { id: 'template_id2', name: 'Template 2', + rowId: 'template_id2', }, { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, @@ -291,25 +347,30 @@ describe('Filters helpers', () => { id: 'template_id1', _id: '2', name: 'Template 1', + rowId: '2', }, { id: 'randomGroupId2', _id: '4', + rowId: '4', name: 'Group 2', - items: [ + subRows: [ { id: 'template_id5', name: 'Template 5', + rowId: 'template_id5', }, { id: 'template_id6', name: 'Template 6', + rowId: 'template_id6', }, ], }, { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, ]); @@ -318,11 +379,13 @@ describe('Filters helpers', () => { { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, @@ -332,33 +395,39 @@ describe('Filters helpers', () => { { id: 'randomGroupId', _id: '1', + rowId: '1', name: 'Group 1', - items: [ + subRows: [ { id: 'template_id3', name: 'Template 3', + rowId: 'template_id3', }, ], }, { id: 'template_id1', _id: '2', + rowId: '2', name: 'Template 1', }, { id: 'randomGroupId2', _id: '4', + rowId: '4', name: 'Group 2', - items: [ + subRows: [ { id: 'template_id5', name: 'Template 5', + rowId: 'template_id5', }, ], }, { id: 'template_id4', _id: '3', + rowId: '3', name: 'Template 4', }, ]); diff --git a/cypress/e2e/settings/filters.cy.ts b/cypress/e2e/settings/filters.cy.ts index 81b210f697..59968c97c2 100644 --- a/cypress/e2e/settings/filters.cy.ts +++ b/cypress/e2e/settings/filters.cy.ts @@ -43,6 +43,7 @@ describe('Filters', () => { cy.checkA11y(); cy.contains('button', 'Add').click(); }); + cy.contains('tr', 'My filter group 1').contains('button', 'Group').click(); }); it('should check that the table is accessible', () => { @@ -72,137 +73,146 @@ describe('Filters', () => { cy.contains('button', 'Dismiss').click(); }); - it('should edit the group', () => { - cy.contains('td', 'My filter group 1').parent().contains('button', 'Edit').click(); - cy.get('aside').within(() => { - cy.contains('Edit group'); - cy.get('#group-name').clear(); - cy.get('#group-name').type('Ordenes', { delay: 0 }); - cy.contains('button', 'Update').click(); + describe('groups', () => { + it('should edit the group', () => { + cy.contains('tr', 'My filter group 1').contains('button', 'Edit').click(); + cy.get('aside').within(() => { + cy.contains('Edit group'); + cy.get('#group-name').clear(); + cy.get('#group-name').type('Ordenes', { delay: 0 }); + cy.contains('button', 'Update').click(); + }); + cy.contains('td', 'Ordenes'); }); - cy.contains('td', 'Ordenes'); - }); - it('should show the available and currentyl selected templates', () => { - cy.contains('td', 'Ordenes').parent().contains('button', 'Edit').click(); - cy.get('aside').within(() => { - cy.getByTestId('multiselect').within(() => { - cy.get('button').eq(0).click(); - cy.contains('Mecanismo').should('not.exist'); - cy.contains('País').should('not.exist'); - cy.contains('Ordenes de la corte').should('exist'); - cy.contains('Sentencia de la corte').should('exist'); - cy.contains('Ordenes del presidente').should('exist'); - cy.get('button').eq(0).click(); + it('should show the available and currentyl selected templates', () => { + cy.contains('tr', 'Ordenes').contains('button', 'Edit').click(); + cy.get('aside').within(() => { + cy.getByTestId('multiselect').within(() => { + cy.get('button').eq(0).click(); + cy.contains('Mecanismo').should('not.exist'); + cy.contains('País').should('not.exist'); + cy.contains('Ordenes de la corte').should('exist'); + cy.contains('Sentencia de la corte').should('exist'); + cy.contains('Ordenes del presidente').should('exist'); + cy.get('button').eq(0).click(); + }); }); }); - }); - it('should validate group format', () => { - cy.get('aside').within(() => { - cy.get('#group-name').clear(); - cy.contains('button', 'Update').click(); - cy.contains('This field is required'); - cy.get('#group-name').type('Reportes y causas', { delay: 0 }); - cy.getByTestId('multiselect').within(() => { - cy.get('button').eq(1).click(); - cy.get('button').eq(1).click(); - cy.get('button').eq(1).click(); - }); - cy.contains('button', 'Update').click(); - cy.contains('This field is required'); - cy.getByTestId('multiselect').within(() => { - cy.get('button').eq(0).click(); - cy.contains('Causa').click(); - cy.contains('Report').click(); + it('should validate group format', () => { + cy.get('aside').within(() => { + cy.get('#group-name').clear(); + cy.contains('button', 'Update').click(); + cy.contains('This field is required'); + cy.get('#group-name').type('Reportes y causas', { delay: 0 }); + cy.getByTestId('multiselect').within(() => { + cy.get('button').eq(1).click(); + cy.get('button').eq(1).click(); + cy.get('button').eq(1).click(); + }); + cy.contains('button', 'Update').click(); }); - cy.contains('Update').click(); + cy.contains('tr', 'Reportes y causas').contains('Group').click(); + cy.contains('Empty group. Drop here to add'); }); - cy.contains('td', 'Reportes y causas'); - }); - it('should create a another group', () => { - cy.contains('button', 'Add group').click(); - cy.get('aside').within(() => { - cy.get('#group-name').type('Ordenes', { delay: 0 }); - cy.getByTestId('multiselect').within(() => { - cy.get('button').click(); - cy.contains('Voto Separado').click(); - cy.contains('Ordenes del presidente').click(); - cy.contains('Medida Provisional').click(); - cy.contains('Sentencia de la corte').click(); + it('should create a another group', () => { + cy.contains('button', 'Add group').click(); + cy.get('aside').within(() => { + cy.get('#group-name').type('Ordenes', { delay: 0 }); + cy.getByTestId('multiselect').within(() => { + cy.get('button').click(); + cy.contains('Voto Separado').click(); + cy.contains('Ordenes del presidente').click(); + cy.contains('Medida Provisional').click(); + cy.contains('Sentencia de la corte').click(); + }); + cy.contains('button', 'Add').click(); }); - cy.contains('button', 'Add').click(); }); - cy.contains('button', 'Save').click(); - cy.contains('Filters saved'); - cy.contains('button', 'Dismiss').click(); - }); - it('should verify the library is updated', () => { - cy.contains('a', 'Library').click(); - cy.get('#filtersForm > :nth-child(2) > .search__filter').within(() => { - cy.contains('div.multiselectItem', 'Ordenes').within(() => { - cy.get('span.multiselectItem-action').click(); + it('should create an empty group', () => { + cy.contains('button', 'Add group').click(); + cy.get('aside').within(() => { + cy.get('#group-name').type('Other documents', { delay: 0 }); + cy.contains('button', 'Add').click(); }); - cy.get('[title="Voto Separado"]'); - cy.get('[title="Ordenes del presidente"]'); - cy.get('[title="Medida Provisional"]'); - cy.get('[title="Sentencia de la corte"]'); - cy.contains('div.multiselectItem', 'Reportes y causas'); - cy.get('[title="Mecanismo"]'); - cy.get('[title="País"]'); + cy.contains('tr', 'Other documents').contains('button', 'Group').click(); + cy.contains('Empty group. Drop here to add'); }); - }); - describe('deletion', () => { - it('should delete single filters', () => { - cy.contains('a', 'Settings').click(); - cy.contains('a', 'Filter').click(); - cy.contains('caption', 'Filters').click(); - cy.contains('td', 'Mecanismo') - .parent() - .within(() => { - cy.get('input[type=checkbox]').click(); + it('should edit the group and add an item', () => { + cy.contains('tr', 'Other documents').contains('button', 'Edit').click(); + cy.get('aside').within(() => { + cy.getByTestId('multiselect').within(() => { + cy.get('button').click(); + cy.contains('Juez y/o Comisionado').click(); }); - cy.contains('button', 'Delete').click(); + cy.contains('button', 'Update').click(); + }); + cy.contains('tr', 'Other documents').contains('button', 'Group').click(); + cy.contains('tr', 'Juez y/o Comisionado'); + }); + + it('should save and delete empty groups', () => { cy.contains('button', 'Save').click(); cy.contains('Filters saved'); cy.contains('button', 'Dismiss').click(); + cy.contains('tr', 'Reportes y causas').should('not.exist'); }); + }); - it('should delete group items', () => { - cy.contains('td', 'Reportes y causas').within(() => { - cy.contains('button', 'Group').click(); - }); - cy.contains('td', 'Causa') - .parent() - .within(() => { - cy.get('input[type=checkbox]').click(); + describe('check library', () => { + it('should verify the library is updated', () => { + cy.contains('a', 'Library').click(); + cy.get('#filtersForm > :nth-child(2) > .search__filter').within(() => { + cy.get('[title="Mecanismo"]'); + cy.get('[title="País"]'); + cy.contains('div.multiselectItem', 'Ordenes').within(() => { + cy.get('span.multiselectItem-action').click(); + }); + cy.get('[title="Voto Separado"]'); + cy.get('[title="Ordenes del presidente"]'); + cy.get('[title="Medida Provisional"]'); + cy.get('[title="Sentencia de la corte"]'); + cy.contains('div.multiselectItem', 'Other documents').within(() => { + cy.get('span.multiselectItem-action').click(); }); + cy.get('[title="Juez y/o Comisionado"]'); + cy.contains('div.multiselectItem', 'Reportes y causas').should('not.exist'); + }); + }); + }); + describe('deletion', () => { + it('should delete single filters', () => { + cy.contains('a', 'Settings').click(); + cy.contains('a', 'Filter').click(); + cy.contains('caption', 'Filters').click(); + cy.contains('tr', 'Mecanismo').contains('Select').click(); cy.contains('button', 'Delete').click(); cy.contains('button', 'Save').click(); cy.contains('Filters saved'); cy.contains('button', 'Dismiss').click(); }); - it('should delete a group', () => { - cy.contains('td', 'Ordenes') - .parent() - .within(() => { - cy.get('input[type=checkbox]').click(); - }); + it('should delete group items and an entire group', () => { + cy.contains('tr', 'Ordenes').contains('button', 'Group').click(); + cy.contains('tr', 'Medida Provisional').contains('Select').click(); + cy.contains('tr', 'Other documents').contains('Select').click(); cy.contains('button', 'Delete').click(); cy.contains('button', 'Save').click(); cy.contains('Filters saved'); cy.contains('button', 'Dismiss').click(); }); - it('should check sepecifically deleted items', () => { - cy.contains('td', 'Mecanismo').should('not.exist'); - cy.contains('td', 'Causa').should('not.exist'); - cy.contains('td', 'Ordenes').should('not.exist'); + it('should check the results', () => { + cy.contains('td', 'País'); + cy.contains('tr', 'Ordenes').contains('button', 'Group'); + cy.contains('td', 'Voto Separado'); + cy.contains('td', 'Ordenes del presidente'); + cy.contains('td', 'Sentencia de la corte'); }); }); }); From 9f71a0009a0fddaee3982769ce5a42d4be244eff Mon Sep 17 00:00:00 2001 From: Santiago Date: Wed, 28 Aug 2024 14:44:32 -0300 Subject: [PATCH 16/36] typo fix --- cypress/e2e/settings/menu.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/e2e/settings/menu.cy.ts b/cypress/e2e/settings/menu.cy.ts index 373702adf1..06e67150a2 100644 --- a/cypress/e2e/settings/menu.cy.ts +++ b/cypress/e2e/settings/menu.cy.ts @@ -54,7 +54,7 @@ describe('Menu configuration', () => { cy.getByTestId('menu-save').should('be.disabled'); }); - it('should not should the unsaved changes alert', () => { + it('should not show the unsaved changes alert', () => { cy.contains('a', 'Account').click(); cy.contains('a', 'Menu').click(); cy.contains('caption', 'Menu'); From 84e2a4458d389fb13d6dc6560a5c9097ee868f87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:54:12 -0300 Subject: [PATCH 17/36] Bump react-hook-form from 7.52.2 to 7.53.0 (#7162) Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.52.2 to 7.53.0. - [Release notes](https://github.com/react-hook-form/react-hook-form/releases) - [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md) - [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.52.2...v7.53.0) --- updated-dependencies: - dependency-name: react-hook-form dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2c93e684c5..a63c1cd9c5 100644 --- a/package.json +++ b/package.json @@ -207,7 +207,7 @@ "react-dom": "^18.3.1", "react-dropzone": "14.2.3", "react-helmet": "^6.1.0", - "react-hook-form": "^7.52.1", + "react-hook-form": "^7.53.0", "react-image-gallery": "^1.3.0", "react-modal": "^3.16.1", "react-player": "^2.13.0", diff --git a/yarn.lock b/yarn.lock index 9e7b9c03f7..8ac8acb644 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15210,10 +15210,10 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" -react-hook-form@^7.52.1: - version "7.52.2" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.2.tgz#ff40f4776250b86ddfcde6be68d34aa82b1c60fe" - integrity sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A== +react-hook-form@^7.53.0: + version "7.53.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab" + integrity sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ== react-icons@5.2.1: version "5.2.1" From 5971779663c47e1d704e3ca9df2e565bde94b8c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 12:36:14 -0300 Subject: [PATCH 18/36] Bump eslint-plugin-cypress from 3.3.0 to 3.5.0 (#7164) Bumps [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress) from 3.3.0 to 3.5.0. - [Release notes](https://github.com/cypress-io/eslint-plugin-cypress/releases) - [Commits](https://github.com/cypress-io/eslint-plugin-cypress/compare/v3.3.0...v3.5.0) --- updated-dependencies: - dependency-name: eslint-plugin-cypress dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Santiago <71732018+Zasa-san@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a63c1cd9c5..982fdd5cc0 100644 --- a/package.json +++ b/package.json @@ -350,7 +350,7 @@ "enzyme-to-json": "^3.6.2", "eslint": "v8.57.0", "eslint-config-airbnb": "19.0.4", - "eslint-plugin-cypress": "^3.3.0", + "eslint-plugin-cypress": "^3.5.0", "eslint-plugin-import": "v2.29.1", "eslint-plugin-jasmine": "4.2.0", "eslint-plugin-jest": "v28.6.0", diff --git a/yarn.lock b/yarn.lock index 8ac8acb644..76eac33309 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9265,10 +9265,10 @@ eslint-module-utils@^2.8.0: dependencies: debug "^3.2.7" -eslint-plugin-cypress@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-3.3.0.tgz#4ab963193d21ad22aca8379e1d15ba02619ae8db" - integrity sha512-HPHMPzYBIshzJM8wqgKSKHG2p/8R0Gbg4Pb3tcdC9WrmkuqxiKxSKbjunUrajhV5l7gCIFrh1P7C7GuBqH6YuQ== +eslint-plugin-cypress@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-3.5.0.tgz#380ef5049ad80ebeca923db69e4aa96e72fcd893" + integrity sha512-JZQ6XnBTNI8h1B9M7wJSFzc48SYbh7VMMKaNTQOFa3BQlnmXPrVc4PKen8R+fpv6VleiPeej6VxloGb42zdRvw== dependencies: globals "^13.20.0" From f3c0cb173ff23161d26ba46b268d62a998821212 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 12:55:05 -0300 Subject: [PATCH 19/36] fix filters behavior --- .../Routes/Settings/Filters/FiltersTable.tsx | 32 +++++---- .../Filters/components/specs/fixtures.ts | 72 +++++++++++++++++++ .../Filters/components/specs/helpers.spec.ts | 71 +----------------- 3 files changed, 92 insertions(+), 83 deletions(-) create mode 100644 app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts diff --git a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx index 12cb9d3bfb..cdd6f08116 100644 --- a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx +++ b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx @@ -1,6 +1,6 @@ /* eslint-disable max-statements */ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { LoaderFunction, useBlocker, useLoaderData } from 'react-router-dom'; +import { LoaderFunction, useBlocker, useLoaderData, useRevalidator } from 'react-router-dom'; import { useSetAtom } from 'jotai'; import { IncomingHttpHeaders } from 'http'; import { RowSelectionState } from '@tanstack/react-table'; @@ -38,7 +38,7 @@ const filtersLoader = const FiltersTable = () => { const { filters: loadedFilters = [], templates: loadedTemplates } = useLoaderData() as LoaderData; - const prevFilters = useRef(loadedFilters); + const currentFilters = useRef(loadedFilters); const [hasChanges, setHasChanges] = useState(false); const [disabled, setDisabled] = useState(false); const [showModal, setShowModal] = useState(false); @@ -50,6 +50,7 @@ const FiltersTable = () => { const setAtom = useSetAtom(sidepanelAtom); const setNotifications = useSetAtom(notificationAtom); const setSettings = useSetAtom(settingsAtom); + const revalidator = useRevalidator(); const templates = useMemo( () => filterAvailableTemplates(loadedTemplates, filters), @@ -57,12 +58,19 @@ const FiltersTable = () => { ); useEffect(() => { - if (JSON.stringify(filters) !== JSON.stringify(prevFilters.current)) { + const formattedFilters = formatFilters(loadedFilters || []); + currentFilters.current = formattedFilters; + setFilters(formattedFilters); + }, [loadedFilters]); + + useEffect(() => { + currentFilters.current = filters; + if (JSON.stringify(currentFilters.current) !== JSON.stringify(loadedFilters)) { setHasChanges(true); } else { setHasChanges(false); } - }, [filters]); + }, [filters, loadedFilters]); useEffect(() => { if (blocker.state === 'blocked') { @@ -76,12 +84,12 @@ const FiltersTable = () => { const addNewFilters = (templatedIds: string[]) => { const newFilters = createNewFilters(templatedIds, templates); - setFilters([...filters, ...newFilters]); + setFilters([...currentFilters.current, ...newFilters]); }; const handleDelete = () => { const idsToRemove: string[] = []; - filters?.forEach(filter => { + currentFilters.current?.forEach(filter => { if (filter.rowId in selectedFilters) { idsToRemove.push(filter.rowId); } @@ -94,14 +102,14 @@ const FiltersTable = () => { } }); - const updatedFilters = deleteFilters(filters, idsToRemove); + const updatedFilters = deleteFilters(currentFilters.current, idsToRemove); setFilters(updatedFilters || []); }; const handleSave = async () => { setDisabled(true); - const savedFilters = sanitizeFilters(filters); - const response = await settingsAPI.save({ filters: savedFilters }); + const filtersToSave = sanitizeFilters(currentFilters.current); + const response = await settingsAPI.save({ filters: filtersToSave }); if (response instanceof FetchResponseError) { return setNotifications({ type: 'error', @@ -112,9 +120,7 @@ const FiltersTable = () => { setSettings(response); setDisabled(false); setHasChanges(false); - const formattedFilters = formatFilters(savedFilters || []); - prevFilters.current = formattedFilters; - setFilters(formattedFilters); + revalidator.revalidate(); return setNotifications({ type: 'success', text: Filters saved }); }; @@ -155,7 +161,7 @@ const FiltersTable = () => { dnd={{ enable: true }} enableSelections onChange={({ rows, selectedRows }) => { - setFilters(rows); + currentFilters.current = rows; setSelectedFilters(selectedRows); }} columns={createColumns(setShowSidepanel)} diff --git a/app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts b/app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts new file mode 100644 index 0000000000..a1326f5f50 --- /dev/null +++ b/app/react/V2/Routes/Settings/Filters/components/specs/fixtures.ts @@ -0,0 +1,72 @@ +import { Filter } from '../helpers'; + +const templates = [ + { + _id: 'id1', + name: 'Template 1', + properties: [], + }, + { + _id: 'id2', + name: 'Template 2', + properties: [], + }, + { + _id: 'id3', + name: 'Template 3', + properties: [], + }, + { + _id: 'id4', + name: 'Template 4', + properties: [], + }, +]; + +const filters: Filter[] = [ + { + id: 'randomGroupId', + _id: '1', + rowId: '1', + name: 'Group 1', + subRows: [ + { + id: 'template_id2', + rowId: 'template_id2', + name: 'Template 2', + }, + { + id: 'template_id3', + rowId: 'template_id3', + name: 'Template 3', + }, + ], + }, + { + id: 'template_id1', + _id: '2', + rowId: '2', + name: 'Template 1', + }, + { + id: 'randomGroupId2', + _id: '4', + rowId: '4', + name: 'Group 2', + subRows: [ + { + id: 'template_id5', + rowId: 'template_id5', + name: 'Template 5', + }, + ], + }, + { + id: 'template_id4', + _id: '3', + rowId: '3', + name: 'Template 4', + }, +]; + +export { templates, filters }; diff --git a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts index 7e52756043..b538dfdd24 100644 --- a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts +++ b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts @@ -3,77 +3,8 @@ import { deleteFilters, updateFilters, sanitizeFilters, - Filter, } from '../helpers'; - -const templates = [ - { - _id: 'id1', - name: 'Template 1', - properties: [], - }, - { - _id: 'id2', - name: 'Template 2', - properties: [], - }, - { - _id: 'id3', - name: 'Template 3', - properties: [], - }, - { - _id: 'id4', - name: 'Template 4', - properties: [], - }, -]; - -const filters: Filter[] = [ - { - id: 'randomGroupId', - _id: '1', - rowId: '1', - name: 'Group 1', - subRows: [ - { - id: 'template_id2', - rowId: 'template_id2', - name: 'Template 2', - }, - { - id: 'template_id3', - rowId: 'template_id3', - name: 'Template 3', - }, - ], - }, - { - id: 'template_id1', - _id: '2', - rowId: '2', - name: 'Template 1', - }, - { - id: 'randomGroupId2', - _id: '4', - rowId: '4', - name: 'Group 2', - subRows: [ - { - id: 'template_id5', - rowId: 'template_id5', - name: 'Template 5', - }, - ], - }, - { - id: 'template_id4', - _id: '3', - rowId: '3', - name: 'Template 4', - }, -]; +import { filters, templates } from './fixtures'; describe('Filters helpers', () => { describe('filterAvailableTemplates', () => { From f14a027e9492415619e40e6538b4bd6057894872 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 13:05:50 -0300 Subject: [PATCH 20/36] remove translate for consistency --- .../V2/Routes/Settings/Filters/components/TableComponents.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx index 5d447a27a4..3fd31f9257 100644 --- a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx @@ -13,9 +13,7 @@ const TitleHeader = () => Label; const ActionHeader = () => Action; const Filters = ({ getValue }: CellContext) => ( -
- {getValue()} -
+
{getValue()}
); const ActionCell = ({ cell, row }: CellContext) => { From 5fac3ae4dd6f35360b08c916c3c496ac5384c098 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 13:24:25 -0300 Subject: [PATCH 21/36] remove unneeded translate --- .../RelationshipTypes/components/TableComponents.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx index 6e7e16cb6b..e6746da3dd 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/no-multi-comp */ import React from 'react'; -import { Translate } from 'app/I18N'; +import { t, Translate } from 'app/I18N'; import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { Button, Pill } from 'app/V2/Components/UI'; import { ClientRelationshipType, Template } from 'app/apiResponseTypes'; @@ -24,10 +24,8 @@ const EditButton = ({ cell, column }: CellContext const TitleCell = ({ cell, getValue }: CellContext) => (
- - {getValue()} - - ({getValue()}) + {t(cell.row.original._id, getValue(), null, false)}( + {getValue()})
); From 67bfe95acf16bd94dc74e9299bb381c931cccc7a Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 13:26:14 -0300 Subject: [PATCH 22/36] sort buttons --- .../V2/Routes/Settings/Filters/FiltersTable.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx index cdd6f08116..5c68061aa5 100644 --- a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx +++ b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx @@ -196,14 +196,6 @@ const FiltersTable = () => {
- +
)} From 85a6bb99ec35763406bc9b20e65aeebf4d8659b0 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 13:36:33 -0300 Subject: [PATCH 23/36] refactor .map --- app/react/V2/Routes/Settings/Filters/components/helpers.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/react/V2/Routes/Settings/Filters/components/helpers.ts b/app/react/V2/Routes/Settings/Filters/components/helpers.ts index 10425c86e8..3e277578bb 100644 --- a/app/react/V2/Routes/Settings/Filters/components/helpers.ts +++ b/app/react/V2/Routes/Settings/Filters/components/helpers.ts @@ -96,10 +96,9 @@ const sanitizeFilters = (filters?: Filter[]) => { } if (subRows) { - sanitizedFilter.items = subRows.map(item => { - const { rowId: itemRowId, _id, ...sanitizedItem } = { ...item }; - return sanitizedItem; - }); + sanitizedFilter.items = subRows.map( + ({ rowId: itemRowId, _id, ...sanitizedItem }) => sanitizedItem + ); } sanitizedFilters.push(sanitizedFilter); From 5b07635c686fa1a1193a12cf75dc59872b8d3207 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:44:54 -0500 Subject: [PATCH 24/36] Bump eslint-plugin-jest from 28.6.0 to 28.8.0 (#7160) Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 28.6.0 to 28.8.0. - [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases) - [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v28.6.0...v28.8.0) --- updated-dependencies: - dependency-name: eslint-plugin-jest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mercy --- package.json | 2 +- yarn.lock | 75 +++++++++++++++++++++++++--------------------------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index 982fdd5cc0..e36ff4318f 100644 --- a/package.json +++ b/package.json @@ -353,7 +353,7 @@ "eslint-plugin-cypress": "^3.5.0", "eslint-plugin-import": "v2.29.1", "eslint-plugin-jasmine": "4.2.0", - "eslint-plugin-jest": "v28.6.0", + "eslint-plugin-jest": "v28.8.0", "eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "5.1.3", diff --git a/yarn.lock b/yarn.lock index 76eac33309..049fde08c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5169,7 +5169,7 @@ resolved "https://registry.yarnpkg.com/@types/json-buffer/-/json-buffer-3.0.0.tgz#85c1ff0f0948fc159810d4b5be35bf8c20875f64" integrity sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ== -"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -5454,7 +5454,7 @@ dependencies: "@types/node" "*" -"@types/semver@^7.3.12", "@types/semver@^7.3.4", "@types/semver@^7.5.8": +"@types/semver@^7.3.12", "@types/semver@^7.3.4": version "7.5.8" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== @@ -5622,13 +5622,13 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" -"@typescript-eslint/scope-manager@7.8.0": - version "7.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz#bb19096d11ec6b87fb6640d921df19b813e02047" - integrity sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g== +"@typescript-eslint/scope-manager@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz#834301d2e70baf924c26818b911bdc40086f7468" + integrity sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg== dependencies: - "@typescript-eslint/types" "7.8.0" - "@typescript-eslint/visitor-keys" "7.8.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" "@typescript-eslint/type-utils@5.62.0": version "5.62.0" @@ -5645,10 +5645,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== -"@typescript-eslint/types@7.8.0": - version "7.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.8.0.tgz#1fd2577b3ad883b769546e2d1ef379f929a7091d" - integrity sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw== +"@typescript-eslint/types@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.3.0.tgz#378e62447c2d7028236e55a81d3391026600563b" + integrity sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw== "@typescript-eslint/typescript-estree@5.62.0": version "5.62.0" @@ -5663,15 +5663,15 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@7.8.0": - version "7.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz#b028a9226860b66e623c1ee55cc2464b95d2987c" - integrity sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg== +"@typescript-eslint/typescript-estree@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz#3e3d38af101ba61a8568f034733b72bfc9f176b9" + integrity sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA== dependencies: - "@typescript-eslint/types" "7.8.0" - "@typescript-eslint/visitor-keys" "7.8.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" debug "^4.3.4" - globby "^11.1.0" + fast-glob "^3.3.2" is-glob "^4.0.3" minimatch "^9.0.4" semver "^7.6.0" @@ -5691,18 +5691,15 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@^6.0.0 || ^7.0.0": - version "7.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.8.0.tgz#57a79f9c0c0740ead2f622e444cfaeeb9fd047cd" - integrity sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ== +"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.3.0.tgz#b10972319deac5959c7a7075d0cf2b5e1de7ec08" + integrity sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.15" - "@types/semver" "^7.5.8" - "@typescript-eslint/scope-manager" "7.8.0" - "@typescript-eslint/types" "7.8.0" - "@typescript-eslint/typescript-estree" "7.8.0" - semver "^7.6.0" + "@typescript-eslint/scope-manager" "8.3.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/typescript-estree" "8.3.0" "@typescript-eslint/visitor-keys@5.62.0": version "5.62.0" @@ -5712,12 +5709,12 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@7.8.0": - version "7.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz#7285aab991da8bee411a42edbd5db760d22fdd91" - integrity sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA== +"@typescript-eslint/visitor-keys@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz#320d747d107af1eef1eb43fbc4ccdbddda13068b" + integrity sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA== dependencies: - "@typescript-eslint/types" "7.8.0" + "@typescript-eslint/types" "8.3.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0": @@ -9308,12 +9305,12 @@ eslint-plugin-jasmine@4.2.0: resolved "https://registry.yarnpkg.com/eslint-plugin-jasmine/-/eslint-plugin-jasmine-4.2.0.tgz#ed0fe988b6e3b123905a7bf68d77239649fd018c" integrity sha512-zSCsnP4gMqBSt8jApExP0ja43nAI1fpAD5kY+knrIJylBxC/LEth25PkqcKJqW32GjesjsiA1SSSR3Z5qIranA== -eslint-plugin-jest@v28.6.0: - version "28.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.6.0.tgz#8410588d60bcafa68a91b6ec272e4a415502302a" - integrity sha512-YG28E1/MIKwnz+e2H7VwYPzHUYU4aMa19w0yGcwXnnmJH6EfgHahTJ2un3IyraUxNfnz/KUhJAFXNNwWPo12tg== +eslint-plugin-jest@v28.8.0: + version "28.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.8.0.tgz#54f597b5a3295ad04ec946baa245ad02b9b2bca0" + integrity sha512-Tubj1hooFxCl52G4qQu0edzV/+EZzPUeN8p2NnW5uu4fbDs+Yo7+qDVDc4/oG3FbCqEBmu/OC3LSsyiU22oghw== dependencies: - "@typescript-eslint/utils" "^6.0.0 || ^7.0.0" + "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" eslint-plugin-jsx-a11y@6.7.1: version "6.7.1" From 008c0a7b838e4fbc8b03dfbdcd16fff052948ab8 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 14:09:07 -0300 Subject: [PATCH 25/36] apply sr-only on the content, not the cell, for actions --- .../Settings/CustomUploads/components/UploadsTable.tsx | 7 ++----- .../Routes/Settings/Filters/components/TableComponents.tsx | 7 ++----- .../Settings/MenuConfig/components/TableComponents.tsx | 4 ++-- app/react/V2/Routes/Settings/Pages/PagesList.tsx | 1 - .../V2/Routes/Settings/Pages/components/PageListTable.tsx | 2 +- .../V2/Routes/Settings/Translations/TranslationsList.tsx | 1 - .../Settings/Translations/components/TableComponents.tsx | 2 +- .../Routes/Settings/Users/components/TableComponents.tsx | 6 +++--- 8 files changed, 11 insertions(+), 19 deletions(-) diff --git a/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx b/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx index 18b94d2580..0ad1f6374b 100644 --- a/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx +++ b/app/react/V2/Routes/Settings/CustomUploads/components/UploadsTable.tsx @@ -10,7 +10,7 @@ const columnHelper = createColumnHelper(); const TitleHeader = () => Name; const PreviewHeader = () => Preview; const URLHeader = () => URL; -const ActionHeader = () => Action; +const ActionHeader = () => Action; const TitleCell = ({ getValue }: CellContext) => getValue(); const URLCell = ({ getValue }: CellContext) => `/assets/${getValue()}`; @@ -83,10 +83,7 @@ const createColumns = ( header: ActionHeader, cell: ActionCell, enableSorting: false, - meta: { - action: () => ({ delete: handleDelete, edit: editFile }), - headerClassName: 'w-0 sr-only', - }, + meta: { action: () => ({ delete: handleDelete, edit: editFile }) }, }), ]; diff --git a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx index 3fd31f9257..1b29cab01b 100644 --- a/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Filters/components/TableComponents.tsx @@ -10,7 +10,7 @@ import { Filter } from './helpers'; const columnHelper = createColumnHelper(); const TitleHeader = () => Label; -const ActionHeader = () => Action; +const ActionHeader = () => Action; const Filters = ({ getValue }: CellContext) => (
{getValue()}
@@ -53,10 +53,7 @@ const createColumns = (setSidepanel: React.Dispatch) => ( const TitleHeader = () => Label; const URLHeader = () => URL; -const ActionHeader = () => Action; +const ActionHeader = () => Action; const columnHelper = createColumnHelper(); const columns = (actions: { edit: Function }) => [ @@ -43,7 +43,7 @@ const columns = (actions: { edit: Function }) => [ header: ActionHeader, cell: EditButton, enableSorting: false, - meta: { action: actions.edit, headerClassName: 'sr-only invisible bg-gray-50' }, + meta: { action: actions.edit }, }), ]; export { EditButton, TitleHeader, URLHeader, TitleCell, columns }; diff --git a/app/react/V2/Routes/Settings/Pages/PagesList.tsx b/app/react/V2/Routes/Settings/Pages/PagesList.tsx index dc08b24ed1..2916f4f76d 100644 --- a/app/react/V2/Routes/Settings/Pages/PagesList.tsx +++ b/app/react/V2/Routes/Settings/Pages/PagesList.tsx @@ -79,7 +79,6 @@ const PagesList = () => { header: ActionHeader, cell: ActionCell, enableSorting: false, - meta: { headerClassName: 'sr-only invisible bg-gray-50' }, }), ]; diff --git a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx index 75bb8c5851..945e85a86f 100644 --- a/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx +++ b/app/react/V2/Routes/Settings/Pages/components/PageListTable.tsx @@ -12,7 +12,7 @@ const getPageUrl = (sharedId: string, title: string) => `page/${sharedId}/${keba const EntityViewHeader = () => Entity Page; const TitleHeader = () => Title; const UrlHeader = () => URL; -const ActionHeader = () => Action; +const ActionHeader = () => Action; const ActionCell = ({ cell }: CellContext) => { const pageUrl = getPageUrl(cell.getValue(), cell.row.original.title); diff --git a/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx b/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx index f0929a165c..c4a450c4d5 100644 --- a/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx +++ b/app/react/V2/Routes/Settings/Translations/TranslationsList.tsx @@ -63,7 +63,6 @@ const TranslationsList = () => { header: ActionHeader, cell: RenderButton, enableSorting: false, - meta: { headerClassName: 'sr-only invisible bg-gray-50' }, }), ]; diff --git a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx index 0923c5be60..a655c06a95 100644 --- a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx @@ -8,7 +8,7 @@ import { TranslationContext } from '../TranslationsList'; const LabelHeader = () => Name; const TypeHeader = () => Type; -const ActionHeader = () => Action; +const ActionHeader = () => Action; const RenderButton = ({ cell }: CellContext) => ( diff --git a/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx index 51738b7643..481be415f8 100644 --- a/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx @@ -16,7 +16,7 @@ const ProtectionHeader = () => Protection; const RoleHeader = () => Role; const GroupsHeader = () => Group; const MembersHeader = () => Members; -const ActionHeader = () => Action; +const ActionHeader = () => Action; const ProtectionPill = ({ cell }: CellContext) => { if (cell.getValue()) { @@ -125,7 +125,7 @@ const getUsersColumns = (editButtonAction: (user: User) => void) => [ id: '1', header: ActionHeader, cell: EditUserButton, - meta: { action: editButtonAction, headerClassName: 'sr-only' }, + meta: { action: editButtonAction }, enableSorting: false, }), ]; @@ -145,7 +145,7 @@ const getGroupsColumns = (editButtonAction: (group: Group) => void) => [ id: '1', header: ActionHeader, cell: EditUserGroupButton, - meta: { action: editButtonAction, headerClassName: 'sr-only' }, + meta: { action: editButtonAction }, enableSorting: false, }), ]; From b0005b8a935cb69c05d9b25d87a088004c85222e Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 15:17:25 -0300 Subject: [PATCH 26/36] correctly handle changes and reset --- .../Routes/Settings/Filters/FiltersTable.tsx | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx index 5c68061aa5..59d55131ca 100644 --- a/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx +++ b/app/react/V2/Routes/Settings/Filters/FiltersTable.tsx @@ -25,6 +25,7 @@ import { LoaderData, sanitizeFilters, formatFilters, + Filter, } from './components'; const filtersLoader = @@ -63,15 +64,6 @@ const FiltersTable = () => { setFilters(formattedFilters); }, [loadedFilters]); - useEffect(() => { - currentFilters.current = filters; - if (JSON.stringify(currentFilters.current) !== JSON.stringify(loadedFilters)) { - setHasChanges(true); - } else { - setHasChanges(false); - } - }, [filters, loadedFilters]); - useEffect(() => { if (blocker.state === 'blocked') { setConfirmNavigationModal(true); @@ -79,7 +71,9 @@ const FiltersTable = () => { }, [blocker, setConfirmNavigationModal]); const cancel = () => { + currentFilters.current = loadedFilters; setFilters(loadedFilters); + setHasChanges(false); }; const addNewFilters = (templatedIds: string[]) => { @@ -124,6 +118,22 @@ const FiltersTable = () => { return setNotifications({ type: 'success', text: Filters saved }); }; + const handleChange = ({ + rows, + selectedRows, + }: { + rows: Filter[]; + selectedRows: RowSelectionState; + }) => { + currentFilters.current = rows; + setSelectedFilters(selectedRows); + if (JSON.stringify(currentFilters.current) !== JSON.stringify(loadedFilters)) { + setHasChanges(true); + } else { + setHasChanges(false); + } + }; + return (
@@ -160,10 +170,7 @@ const FiltersTable = () => {
{ - currentFilters.current = rows; - setSelectedFilters(selectedRows); - }} + onChange={handleChange} columns={createColumns(setShowSidepanel)} data={filters} header={ From f4cad0636d4a96e8e861e9c95ce42e0904a8a221 Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 29 Aug 2024 15:37:41 -0300 Subject: [PATCH 27/36] add rowId afther rerender --- .../V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx index b4b2ebec55..8fd9250b1d 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/RelationshipTypes.tsx @@ -52,6 +52,7 @@ const RelationshipTypes = () => { return { ...relationshipType, + rowId: relationshipType._id, templates: templatesUsingIt, disableRowSelection: Boolean(templatesUsingIt.length), }; From b8fa0a055b0963c19252532bb1bcacf1134ad185 Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 30 Aug 2024 10:20:10 -0300 Subject: [PATCH 28/36] typo fixes --- .../V2/Routes/Settings/CustomUploads/CustomUploads.tsx | 6 +++--- .../Settings/Filters/components/specs/helpers.spec.ts | 2 +- cypress/e2e/settings/filters.cy.ts | 2 +- cypress/e2e/settings/menu.cy.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/react/V2/Routes/Settings/CustomUploads/CustomUploads.tsx b/app/react/V2/Routes/Settings/CustomUploads/CustomUploads.tsx index 1476624666..51382e40c3 100644 --- a/app/react/V2/Routes/Settings/CustomUploads/CustomUploads.tsx +++ b/app/react/V2/Routes/Settings/CustomUploads/CustomUploads.tsx @@ -41,7 +41,7 @@ const CustomUploads = () => { const [confirmationModal, setConfirmationModal] = useState(false); const [showUploadsModal, setShowUploadsModal] = useState(false); const [confirmNavigationModal, setConfirmNavigationModal] = useState(false); - const [showSidepanel, setShowSipanel] = useState(false); + const [showSidepanel, setShowSidepanel] = useState(false); const [modalProps, setModalProps] = useState<{ action: () => void; items: CustomUpload[]; @@ -116,7 +116,7 @@ const CustomUploads = () => {
{ - setShowSipanel(true); + setShowSidepanel(true); setFileToEdit(file); })} onChange={({ selectedRows: selected }) => { @@ -182,7 +182,7 @@ const CustomUploads = () => { setShowSipanel(false)} + closeSidepanel={() => setShowSidepanel(false)} file={fileToEdit} /> diff --git a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts index b538dfdd24..ccead16044 100644 --- a/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts +++ b/app/react/V2/Routes/Settings/Filters/components/specs/helpers.spec.ts @@ -116,7 +116,7 @@ describe('Filters helpers', () => { { id: 'template_id2', name: 'Template 2', - //this _id comes when users drang and drop a root filter inside a group + //this _id comes when users drag and drop a root filter inside a group //@ts-ignore _id: 'rootItemId', rowId: 'rootItemId', diff --git a/cypress/e2e/settings/filters.cy.ts b/cypress/e2e/settings/filters.cy.ts index 59968c97c2..4e3982497a 100644 --- a/cypress/e2e/settings/filters.cy.ts +++ b/cypress/e2e/settings/filters.cy.ts @@ -85,7 +85,7 @@ describe('Filters', () => { cy.contains('td', 'Ordenes'); }); - it('should show the available and currentyl selected templates', () => { + it('should show the available and currently selected templates', () => { cy.contains('tr', 'Ordenes').contains('button', 'Edit').click(); cy.get('aside').within(() => { cy.getByTestId('multiselect').within(() => { diff --git a/cypress/e2e/settings/menu.cy.ts b/cypress/e2e/settings/menu.cy.ts index 06e67150a2..d3834c75ba 100644 --- a/cypress/e2e/settings/menu.cy.ts +++ b/cypress/e2e/settings/menu.cy.ts @@ -93,7 +93,7 @@ describe('Menu configuration', () => { }); }); - it('should save the editied links', () => { + it('should save the edited links', () => { cy.getByTestId('menu-save').click(); cy.contains('Dismiss').click(); cy.wait('@fetchLinks'); From 5392be0d50a50c6fdf30e6858ffd5c4dea8912f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:25:20 -0300 Subject: [PATCH 29/36] Bump mini-css-extract-plugin from 2.9.0 to 2.9.1 (#7167) Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases) - [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md) - [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v2.9.0...v2.9.1) --- updated-dependencies: - dependency-name: mini-css-extract-plugin dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e36ff4318f..e7efe143ba 100644 --- a/package.json +++ b/package.json @@ -370,7 +370,7 @@ "jest-image-snapshot": "^6.4.0", "jest-jasmine2": "^29.7.0", "jest-puppeteer": "6.1.0", - "mini-css-extract-plugin": "^2.9.0", + "mini-css-extract-plugin": "^2.9.1", "mutationobserver-shim": "^0.3.7", "node-polyfill-webpack-plugin": "^4.0.0", "nodemon": "^3.1.4", diff --git a/yarn.lock b/yarn.lock index 049fde08c1..8ad767c878 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13196,10 +13196,10 @@ min-indent@^1.0.0, min-indent@^1.0.1: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-css-extract-plugin@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz#c73a1327ccf466f69026ac22a8e8fd707b78a235" - integrity sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA== +mini-css-extract-plugin@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz#4d184f12ce90582e983ccef0f6f9db637b4be758" + integrity sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ== dependencies: schema-utils "^4.0.0" tapable "^2.2.1" From e45c810cde09bfa3ddd71a99f5129484fdfe0e74 Mon Sep 17 00:00:00 2001 From: Alberto Casado Torres Date: Fri, 30 Aug 2024 16:17:38 +0200 Subject: [PATCH 30/36] testing security fix (#7146) Co-authored-by: Santiago <71732018+Zasa-san@users.noreply.github.com> --- package.json | 3 ++- yarn.lock | 28 ++++------------------------ 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index e7efe143ba..51f580ae19 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,8 @@ "socket.io-parser": "4.2.4", "json-schema": "^0.4.0", "@types/react": "^18.0.25 || ^18.3.1", - "@types/react-dom": "^18.0.9 || ^18.3.0" + "@types/react-dom": "^18.0.9 || ^18.3.0", + "ws": "8.17.1" }, "dependencies": { "@aws-sdk/client-s3": "3.624.0", diff --git a/yarn.lock b/yarn.lock index 8ad767c878..240dd6e34c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18227,30 +18227,10 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== - -ws@^7.3.1: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== - -ws@^8.2.3, ws@^8.8.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" - integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== - -ws@~7.4.2: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== - -ws@~8.11.0: - version "8.11.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" - integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +ws@8.17.1, ws@8.5.0, ws@^7.3.1, ws@^8.2.3, ws@^8.8.0, ws@~7.4.2, ws@~8.11.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xhr@^2.0.1: version "2.6.0" From 28332d7ddda6a70a37e31cd16912403a415377c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:42:08 -0300 Subject: [PATCH 31/36] Bump monaco-editor from 0.50.0 to 0.51.0 (#7165) Bumps [monaco-editor](https://github.com/microsoft/monaco-editor) from 0.50.0 to 0.51.0. - [Release notes](https://github.com/microsoft/monaco-editor/releases) - [Changelog](https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoft/monaco-editor/compare/v0.50.0...v0.51.0) --- updated-dependencies: - dependency-name: monaco-editor dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Santiago <71732018+Zasa-san@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 51f580ae19..853744afd4 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "mime-types": "^2.1.35", "moment": "^2.30.1", "moment-timezone": "0.5.45", - "monaco-editor": "^0.50.0", + "monaco-editor": "^0.51.0", "monaco-editor-webpack-plugin": "^7.1.0", "mongodb": "6.3.0", "mongoose": "8.1.2", diff --git a/yarn.lock b/yarn.lock index 240dd6e34c..289b0b8cb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13336,10 +13336,10 @@ monaco-editor-webpack-plugin@^7.1.0: dependencies: loader-utils "^2.0.2" -monaco-editor@^0.50.0: - version "0.50.0" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.50.0.tgz#44e62b124c8aed224e1d310bbbe6ffd6d5122413" - integrity sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA== +monaco-editor@^0.51.0: + version "0.51.0" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.51.0.tgz#922a6103f6742b5a62fbb097276c5a6619d879db" + integrity sha512-xaGwVV1fq343cM7aOYB6lVE4Ugf0UyimdD/x5PWcWBMKENwectaEu77FAN7c5sFiyumqeJdX1RPTh1ocioyDjw== mongodb-connection-string-url@^3.0.0: version "3.0.0" From 1d195c1b42737ab860758f2681488ec3836d03e2 Mon Sep 17 00:00:00 2001 From: Mercy Date: Fri, 30 Aug 2024 11:54:09 -0500 Subject: [PATCH 32/36] 6699 problems with v2 thesauri (#7132) * using table v2 * using table v2 * code covered by tests * rename test * basic e2e tests * update yarn lock * updates yarn lock * extended e2e thesauri tests * removes death code * ensures selector * remove obsolete translation * clean commands * removes empty groups * edit child * fixes from review * clear import field * restores form reset by the moment * code review fixes * extracts function to reduce complexity * update of snapshots due parenthesis * conditional translate * relies on endpoint for id generation * removes unused import * simplified e2e get of actions * refactor from code review * fix broken component * use uuid for value id --- app/react/App/styles/globals.css | 37 +- .../Metadata/components/MetadataFormFields.js | 18 +- app/react/Routes.tsx | 18 +- app/react/Thesauri/actions/actionTypes.js | 3 - .../actions/specs/thesauriActions.spec.js | 272 +------ .../actions/specs/thesaurisActions.spec.js | 54 -- app/react/Thesauri/actions/thesauriActions.js | 139 ---- .../Thesauri/actions/thesaurisActions.js | 24 - app/react/V2/Components/UI/Button.tsx | 9 +- app/react/V2/Components/UI/TableV2/Table.tsx | 2 +- .../ActivityLog/components/TableElements.tsx | 2 +- .../Settings/IX/components/TableElements.tsx | 2 +- .../Languages/components/TableComponents.tsx | 4 +- .../MenuConfig/components/TableComponents.tsx | 2 +- .../Pages/components/PageListTable.tsx | 4 +- .../components/TableComponents.tsx | 2 +- .../Settings/Thesauri/EditThesaurus.tsx | 220 ++++++ .../Routes/Settings/Thesauri/ThesauriList.tsx | 85 +-- .../Settings/Thesauri/ThesaurusForm.tsx | 399 +++------- .../Thesauri/components/ImportButton.tsx | 24 +- .../Thesauri/components/TableComponents.tsx | 152 ++-- .../components/ThesauriGroupFormSidepanel.tsx | 79 +- .../Thesauri/components/ThesauriTable.tsx | 49 ++ .../components/ThesauriValueFormSidepanel.tsx | 76 +- .../Thesauri/components/ThesaurusActions.tsx | 46 ++ .../Settings/Thesauri/components/index.ts | 4 + .../V2/Routes/Settings/Thesauri/helpers.ts | 204 ++++- .../V2/Routes/Settings/Thesauri/index.ts | 5 +- .../Settings/Thesauri/specs/Thesauri.spec.tsx | 542 +++++++++++++ .../__snapshots__/Thesauri.spec.tsx.snap | 717 ++++++++++++++++++ .../Settings/Thesauri/specs/fixtures.ts | 46 ++ .../Settings/Thesauri/specs/helpers.spec.ts | 466 ++++++++++++ .../components/TableComponents.tsx | 2 +- .../Users/components/TableComponents.tsx | 2 +- app/react/V2/api/thesauri/index.ts | 8 +- app/react/V2/atoms/store.ts | 4 + app/react/V2/shared/testingHelpers.ts | 16 +- app/react/apiResponseTypes.d.ts | 7 +- contents/ui-translations/ar.csv | 2 +- contents/ui-translations/en.csv | 2 +- contents/ui-translations/es.csv | 2 +- contents/ui-translations/fr.csv | 2 +- contents/ui-translations/ko.csv | 2 +- contents/ui-translations/my.csv | 2 +- contents/ui-translations/ru.csv | 2 +- contents/ui-translations/th.csv | 2 +- contents/ui-translations/tr.csv | 2 +- ...ri configuration should add groups #0.png | Bin 0 -> 14271 bytes ...uri configuration should add items #0.png | Bin 0 -> 18159 bytes ...ration should allow to sort by dnd #0.png | Bin 0 -> 19594 bytes ... configuration should delete items #0.png | Bin 0 -> 23210 bytes ... delete an item of a used thesaurus #0.png | Bin 0 -> 12735 bytes ... configuration should edit a group #0.png | Bin 0 -> 19547 bytes ... configuration should edit an item #0.png | Bin 0 -> 18808 bytes ...ation should import items from csv #0.png | Bin 0 -> 26493 bytes ... configuration should keep sorting #0.png | Bin 0 -> 15519 bytes ...iguration should list the thesauri #0.png | Bin 0 -> 38725 bytes ...hould sort the items alphabetically #0.png | Bin 0 -> 29018 bytes ...hould sort the items alphabetically #1.png | Bin 0 -> 39215 bytes ...d update the values in the entities #0.png | Bin 0 -> 26493 bytes cypress/e2e/settings/thesauri.cy.ts | 241 ++++-- cypress/test_files/thesaurus-test.csv | 7 + e2e/regression_suites/settings.test.ts | 22 - 63 files changed, 2838 insertions(+), 1195 deletions(-) create mode 100644 app/react/V2/Routes/Settings/Thesauri/EditThesaurus.tsx create mode 100644 app/react/V2/Routes/Settings/Thesauri/components/ThesauriTable.tsx create mode 100644 app/react/V2/Routes/Settings/Thesauri/components/ThesaurusActions.tsx create mode 100644 app/react/V2/Routes/Settings/Thesauri/components/index.ts create mode 100644 app/react/V2/Routes/Settings/Thesauri/specs/Thesauri.spec.tsx create mode 100644 app/react/V2/Routes/Settings/Thesauri/specs/__snapshots__/Thesauri.spec.tsx.snap create mode 100644 app/react/V2/Routes/Settings/Thesauri/specs/fixtures.ts create mode 100644 app/react/V2/Routes/Settings/Thesauri/specs/helpers.spec.ts create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should add groups #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should add items #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should allow to sort by dnd #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should delete items #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should do ask for confirmation when delete an item of a used thesaurus #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should edit a group #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should edit an item #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should import items from csv #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should keep sorting #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should list the thesauri #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should sort the items alphabetically #0.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should sort the items alphabetically #1.png create mode 100644 cypress/e2e/settings/__image_snapshots__/Thesauri configuration should update the values in the entities #0.png create mode 100644 cypress/test_files/thesaurus-test.csv diff --git a/app/react/App/styles/globals.css b/app/react/App/styles/globals.css index a24420e1a0..9d9efc6a2f 100644 --- a/app/react/App/styles/globals.css +++ b/app/react/App/styles/globals.css @@ -1866,6 +1866,10 @@ input[type="range"]::-ms-fill-lower { display: contents; } +.\!hidden { + display: none !important; +} + .hidden { display: none; } @@ -2508,6 +2512,10 @@ input[type="range"]::-ms-fill-lower { white-space: nowrap; } +.text-wrap { + text-wrap: wrap; +} + .text-nowrap { text-wrap: nowrap; } @@ -2615,6 +2623,11 @@ input[type="range"]::-ms-fill-lower { border-bottom-width: 1px; } +.border-y-0 { + border-top-width: 0px; + border-bottom-width: 0px; +} + .border-b { border-bottom-width: 1px; } @@ -2722,6 +2735,11 @@ input[type="range"]::-ms-fill-lower { border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.border-gray-400 { + --tw-border-opacity: 1; + border-color: rgb(156 163 175 / var(--tw-border-opacity)); +} + .border-gray-50 { --tw-border-opacity: 1; border-color: rgb(249 250 251 / var(--tw-border-opacity)); @@ -3637,11 +3655,6 @@ input[type="range"]::-ms-fill-lower { box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } -.ring-indigo-100 { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(229 237 255 / var(--tw-ring-opacity)); -} - .blur { --tw-blur: blur(8px); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); @@ -4023,6 +4036,11 @@ input[type="range"]::-ms-fill-lower { background-color: rgb(190 24 93 / var(--tw-bg-opacity)); } +.enabled\:hover\:bg-primary-50:hover:enabled { + --tw-bg-opacity: 1; + background-color: rgb(238 242 255 / var(--tw-bg-opacity)); +} + .enabled\:hover\:bg-primary-800:hover:enabled { --tw-bg-opacity: 1; background-color: rgb(55 48 163 / var(--tw-bg-opacity)); @@ -4142,11 +4160,6 @@ input[type="range"]::-ms-fill-lower { color: rgb(205 219 254 / var(--tw-text-opacity)); } -.disabled\:text-indigo-300:disabled { - --tw-text-opacity: 1; - color: rgb(180 198 252 / var(--tw-text-opacity)); -} - .disabled\:text-primary-300:disabled { --tw-text-opacity: 1; color: rgb(165 180 252 / var(--tw-text-opacity)); @@ -4183,6 +4196,10 @@ input[type="range"]::-ms-fill-lower { border-color: rgb(255 255 255 / var(--tw-border-opacity)); } +.has-\[span\:not\(\.active\)\]\:hidden:has(span:not(.active)) { + display: none; +} + :is(.dark .dark\:border-gray-600) { --tw-border-opacity: 1; border-color: rgb(75 85 99 / var(--tw-border-opacity)); diff --git a/app/react/Metadata/components/MetadataFormFields.js b/app/react/Metadata/components/MetadataFormFields.js index 912e609de9..03358cb6fd 100644 --- a/app/react/Metadata/components/MetadataFormFields.js +++ b/app/react/Metadata/components/MetadataFormFields.js @@ -1,19 +1,19 @@ /* eslint-disable max-statements */ /* eslint-disable max-lines */ -import { FormGroup } from 'app/Forms'; -import { t, Translate } from 'app/I18N'; -import { preloadOptionsLimit } from 'shared/config'; +import React, { Component } from 'react'; import Immutable from 'immutable'; import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import ID from 'shared/uniqueID'; +import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { Field, actions as formActions } from 'react-redux-form'; -import { propertyTypes } from 'shared/propertyTypes'; +import uuid from 'node-uuid'; +import { FormGroup } from 'app/Forms'; +import { t, Translate } from 'app/I18N'; import { getSuggestions } from 'app/Metadata/actions/actions'; -import { generateID } from 'shared/IDGenerator'; -import { bindActionCreators } from 'redux'; import Tip from 'app/Layout/Tip'; +import { propertyTypes } from 'shared/propertyTypes'; +import { preloadOptionsLimit } from 'shared/config'; +import { generateID } from 'shared/IDGenerator'; import { saveThesaurus } from 'app/Thesauri/actions/thesauriActions'; import { sanitizeThesauri } from 'app/V2/Routes/Settings/Thesauri/helpers'; @@ -85,7 +85,7 @@ class MetadataFormFields extends Component { async onAddThesauriValueSaved(thesauri, newValue, _model, isMultiSelect) { const { model, push, change } = this.props; const newThesauri = thesauri.toJS(); - const newValueItem = { label: newValue.value, id: ID() }; + const newValueItem = { label: newValue.value, id: uuid.v4() }; if (newValue.group === 'root') { newThesauri.values.push(newValueItem); } else { diff --git a/app/react/Routes.tsx b/app/react/Routes.tsx index 5009080494..6a50f54f0d 100644 --- a/app/react/Routes.tsx +++ b/app/react/Routes.tsx @@ -26,10 +26,10 @@ import { import { Dashboard, dashboardLoader } from 'V2/Routes/Settings/Dashboard/Dashboard'; import { - ThesaurusForm, - theasauriListLoader, + EditThesaurus, + thesauriLoader, ThesauriList, - editTheasaurusLoader, + editThesaurusLoader, } from 'app/V2/Routes/Settings/Thesauri'; import { MenuConfig, menuConfigloader } from 'V2/Routes/Settings/MenuConfig/MenuConfig'; @@ -147,16 +147,12 @@ const getRoutesLayout = ( - )} - loader={theasauriListLoader(headers)} - /> - )} /> + )} loader={thesauriLoader(headers)} /> + )} /> )} - loader={editTheasaurusLoader(headers)} + element={adminsOnlyRoute()} + loader={editThesaurusLoader(headers)} /> { - let dispatch; - let getState; - beforeEach(() => { mockID(); - - dispatch = jasmine.createSpy('dispatch'); - getState = jasmine - .createSpy('getState') - .and.returnValue({ thesauri: { data: { values: [{ label: 'something' }, { label: '' }] } } }); backend.restore(); backend.post(`${APIURL}thesauris`, { body: JSON.stringify({ testBackendResult: 'ok' }) }); }); @@ -61,263 +50,4 @@ describe('thesaurisActions', () => { expect(JSON.parse(backend.lastOptions(`${APIURL}thesauris`).body)).toEqual(data); }); }); - - describe('importThesaurus', () => { - const mockSuperAgent = (url = `${APIURL}thesauris`) => { - const mockUpload = superagent.post(url); - spyOn(mockUpload, 'field').and.returnValue(mockUpload); - spyOn(mockUpload, 'attach').and.returnValue(mockUpload); - spyOn(superagent, 'post').and.returnValue(mockUpload); - return mockUpload; - }; - it('should save thesaurus, import csv data and notify', done => { - const thesaurus = { _id: 'foo', name: 'Bar', values: [] }; - const store = mockStore({}); - const mockUpload = mockSuperAgent(); - const file = { - name: 'filename.csv', - }; - - const resp = { _id: 'foo', name: 'Bar', values: [{ label: 'val' }] }; - - const expectedActions = [ - { type: types.THESAURI_SAVED }, - { - type: notificationsTypes.NOTIFY, - notification: { message: 'Data imported', type: 'success', id: 'unique_id' }, - }, - { - type: 'rrf/change', - model: 'thesauri.data', - value: resp, - silent: false, - multi: false, - external: true, - }, - ]; - - store.dispatch(actions.importThesaurus(thesaurus, file)).then(() => { - expect(superagent.post).toHaveBeenCalledWith(`${APIURL}thesauris`); - expect(mockUpload.attach).toHaveBeenCalledWith('file', file, file.name); - expect(mockUpload.field).toHaveBeenCalledWith('thesauri', JSON.stringify(thesaurus)); - expect(store.getActions()).toEqual(expectedActions); - done(); - }); - - mockUpload.emit('response', { text: JSON.stringify(resp), status: 200 }); - }); - - it.each` - property | message - ${'error'} | ${'unformatted error'} - ${'prettyMessage'} | ${'formatted error'} - `('should notify error if error is returned', ({ property, message }, done) => { - const thesaurus = {}; - const file = {}; - const store = mockStore({}); - const mockUpload = mockSuperAgent(); - - const expectedActions = [ - { - type: notificationsTypes.NOTIFY, - notification: { message, type: 'danger', id: 'unique_id' }, - }, - ]; - - store.dispatch(actions.importThesaurus(thesaurus, file)).then(() => { - expect(superagent.post).toHaveBeenCalledWith(`${APIURL}thesauris`); - expect(store.getActions()).toEqual(expectedActions); - done(); - }); - - mockUpload.emit('response', { - text: JSON.stringify({ [property]: message }), - status: 400, - }); - }); - }); - - describe('addValue()', () => { - it('should add an empty value to the thesauri', () => { - getState.and.returnValue({ thesauri: { data: { values: [{ label: 'something' }] } } }); - spyOn(formActions, 'change'); - actions.addValue()(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', [ - { label: 'something' }, - { label: '', id: 'unique_id' }, - ]); - }); - }); - - describe('addGroup()', () => { - it('should add a new group at the end', () => { - spyOn(formActions, 'change'); - actions.addGroup()(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', [ - { label: 'something' }, - { label: '', id: 'unique_id', values: [{ label: '', id: 'unique_id' }] }, - ]); - }); - }); - - describe('removeValue()', () => { - it('should remove the value from the list', () => { - getState.and.returnValue({ - thesauri: { - data: { - values: [ - { label: 'B', id: 1 }, - { - label: 'A', - id: 2, - values: [{ label: 'D', id: 3 }, { label: 'C', id: 4 }, { label: '' }], - }, - { label: '' }, - ], - }, - }, - }); - spyOn(formActions, 'change'); - actions.removeValue(0)(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', [ - { - label: 'A', - id: 2, - values: [{ label: 'D', id: 3 }, { label: 'C', id: 4 }, { label: '' }], - }, - { label: '' }, - ]); - - actions.removeValue(0, 1)(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', [ - { label: 'B', id: 1 }, - { label: 'A', id: 2, values: [{ label: 'C', id: 4 }, { label: '' }] }, - { label: '' }, - ]); - }); - it('should remove value from group even when group has index 0', () => { - getState.and.returnValue({ - thesauri: { - data: { - values: [ - { - label: 'A', - id: 2, - values: [{ label: 'D', id: 3 }, { label: 'C', id: 4 }, { label: '' }], - }, - { label: 'B', id: 1 }, - { label: '' }, - ], - }, - }, - }); - spyOn(formActions, 'change'); - actions.removeValue(1, 0)(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', [ - { label: 'A', id: 2, values: [{ label: 'D', id: 3 }, { label: '' }] }, - { label: 'B', id: 1 }, - { label: '' }, - ]); - }); - }); - - describe('sortValues()', () => { - it('should sort the values', () => { - getState.and.returnValue({ - thesauri: { - data: { - values: [{ label: 'B' }, { label: 'A', values: [{ label: 'D' }, { label: 'C' }] }], - }, - }, - }); - spyOn(formActions, 'change'); - actions.sortValues()(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', [ - { label: 'A', values: [{ label: 'C' }, { label: 'D' }] }, - { label: 'B' }, - ]); - }); - }); - - describe('updateValues', () => { - let values; - beforeEach(() => { - values = [ - { label: '1', id: '1' }, - { label: '2', id: '2' }, - { label: '3', id: '3' }, - { label: '', id: '4' }, - ]; - spyOn(formActions, 'change'); - }); - - it('should set the updated values in the store', () => { - actions.updateValues(values)(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', values); - }); - it('should update values inside group if group index is provided', () => { - getState.and.returnValue({ - thesauri: { - data: { - values: [ - { label: 'root1', id: 'root1' }, - { label: 'group', id: 'group', values: [] }, - ], - }, - }, - }); - actions.updateValues(values, 1)(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', [ - { label: 'root1', id: 'root1' }, - { label: 'group', id: 'group', values }, - ]); - }); - it('should not remove a group from the root list', () => { - getState.and.returnValue({ - thesauri: { - data: { - values: [ - { label: 'root1', id: 'root1' }, - { label: 'group', id: 'group', values: [] }, - ], - }, - }, - }); - actions.updateValues(values)(dispatch, getState); - expect(formActions.change).not.toHaveBeenCalled(); - }); - it('should not move a group inside a group', () => { - getState.and.returnValue({ - thesauri: { - data: { - values: [ - { label: 'root1', id: 'root1' }, - { label: 'group', id: 'group', values: [] }, - ], - }, - }, - }); - values[2].values = [{ label: '3.1', id: '3.1' }]; - actions.updateValues(values, 1)(dispatch, getState); - expect(formActions.change).not.toHaveBeenCalled(); - }); - describe('if there is a single empty item inside the list', () => { - it('should move the empty item to the bottom of the list', () => { - const newValues = [...values]; - const expectedValues = [...values]; - newValues.push({ label: '5', id: '5' }); - expectedValues.splice(3, 0, { label: '5', id: '5' }); - actions.updateValues(newValues)(dispatch, getState); - expect(formActions.change).toHaveBeenCalledWith('thesauri.data.values', expectedValues); - }); - }); - describe('if there are multiple empty items in the list', () => { - it('should not update the store', () => { - values.push({ label: '', id: '5' }); - values.push({ label: '6', id: '6' }); - actions.updateValues(values)(dispatch, getState); - expect(formActions.change).not.toHaveBeenCalled(); - }); - }); - }); }); diff --git a/app/react/Thesauri/actions/specs/thesaurisActions.spec.js b/app/react/Thesauri/actions/specs/thesaurisActions.spec.js index ac2582e388..8ce057a1d4 100644 --- a/app/react/Thesauri/actions/specs/thesaurisActions.spec.js +++ b/app/react/Thesauri/actions/specs/thesaurisActions.spec.js @@ -1,24 +1,10 @@ -/** @format */ - import backend from 'fetch-mock'; import { APIURL } from 'app/config.js'; import * as actions from 'app/Thesauri/actions/thesaurisActions'; -import { actions as formActions } from 'react-redux-form'; import api from 'app/Thesauri/ThesauriAPI'; describe('thesaurisActions', () => { - describe('editThesaurus', () => { - it('should set the thesauri in the form ', () => { - const thesauri = { name: 'Secret list of things', values: [] }; - const dispatch = jasmine.createSpy('dispatch'); - spyOn(formActions, 'load'); - actions.editThesaurus(thesauri)(dispatch); - - expect(formActions.load).toHaveBeenCalledWith('thesauri.data', thesauri); - }); - }); - describe('async action', () => { let dispatch; @@ -39,46 +25,6 @@ describe('thesaurisActions', () => { afterEach(() => backend.restore()); - describe('deleteThesauri', () => { - it('should delete the thesauri and dispatch a thesauris/REMOVE action with the thesauri', done => { - const thesauri = { _id: 'thesauriId' }; - actions - .deleteThesaurus(thesauri)(dispatch) - .then(() => { - expect(dispatch).toHaveBeenCalledWith({ type: 'dictionaries/REMOVE', value: thesauri }); - done(); - }); - }); - }); - - describe('checkThesauriCanBeDeleted', () => { - it('should return a promise if the thesauri is NOT been use', done => { - const data = { _id: 'thesauriWithoutTemplates' }; - actions - .checkThesaurusCanBeDeleted(data)(dispatch) - .then(() => { - done(); - }) - .catch(() => { - expect('Promise not to be rejected').toBe(false); - done(); - }); - }); - - it('should reject a promise if the thesauri IS been use', done => { - const data = { _id: 'thesauriWithTemplates' }; - actions - .checkThesaurusCanBeDeleted(data)(dispatch) - .then(() => { - expect('Promise to be rejected').toBe(false); - done(); - }) - .catch(() => { - done(); - }); - }); - }); - describe('reloadThesauris', () => { it('should set thesauris to new values', done => { spyOn(api, 'get').and.callFake(async () => Promise.resolve('thesaurisResponse')); diff --git a/app/react/Thesauri/actions/thesauriActions.js b/app/react/Thesauri/actions/thesauriActions.js index 37874d09fe..0359a4d8ff 100644 --- a/app/react/Thesauri/actions/thesauriActions.js +++ b/app/react/Thesauri/actions/thesauriActions.js @@ -1,12 +1,9 @@ import { actions as formActions } from 'react-redux-form'; import { t } from 'app/I18N'; -import ID from 'shared/uniqueID'; import * as types from 'app/Thesauri/actions/actionTypes'; import api from 'app/Thesauri/ThesauriAPI'; import * as notifications from 'app/Notifications/actions/notificationsActions'; -import { advancedSort } from 'app/utils/advancedSort'; import { RequestParams } from 'app/utils/RequestParams'; -import { httpRequest } from 'shared/superagent'; export function saveThesaurus(thesaurus) { return dispatch => @@ -16,139 +13,3 @@ export function saveThesaurus(thesaurus) { dispatch(formActions.change('thesauri.data', _thesauri)); }); } - -export function importThesaurus(thesaurus, file) { - return async dispatch => { - try { - const headers = { - Accept: 'application/json', - 'X-Requested-With': 'XMLHttpRequest', - }; - const fields = { - thesauri: JSON.stringify(thesaurus), - }; - - const data = await httpRequest('thesauris', fields, headers, file); - dispatch({ type: types.THESAURI_SAVED }); - notifications.notify(t('System', 'Data imported', null, false), 'success')(dispatch); - dispatch(formActions.change('thesauri.data', data)); - } catch (e) { - notifications.notify( - t('System', e.prettyMessage || e.error, null, false), - 'danger' - )(dispatch); - } - }; -} - -export function sortValues() { - return (dispatch, getState) => { - let values = getState().thesauri.data.values.slice(0); - values = advancedSort(values, { property: 'label' }); - values = values.map(value => ({ - ...value, - ...(value.values - ? { values: advancedSort(value.values.slice(0), { property: 'label' }) } - : {}), - })); - dispatch(formActions.change('thesauri.data.values', values)); - }; -} - -function moveEmptyItemToBottom(values) { - const _values = [...values]; - const emptyIdx = _values.reduce((found, value, index) => { - if (!value.label && index < _values.length) { - return found.concat([index]); - } - return found; - }, []); - if (emptyIdx.length > 1) { - return null; - } - if (emptyIdx.length === 1) { - const index = emptyIdx[0]; - const emptyValue = _values[index]; - _values.splice(index, 1); - _values.push(emptyValue); - } - return _values; -} - -function areGroupsRemovedFromList(newValues, oldValues) { - return oldValues.some(item => { - if (!item.values) { - return false; - } - return !newValues.some(oldItem => oldItem.id === item.id); - }); -} - -function listContainsGroups(values) { - return values.some(value => value.values); -} - -export function updateValues(updatedValues, groupIndex) { - return (dispatch, getState) => { - const values = getState().thesauri.data.values.slice(0); - const _updatedValues = moveEmptyItemToBottom(updatedValues); - if (!_updatedValues) { - return; - } - if (groupIndex !== undefined) { - if (listContainsGroups(_updatedValues)) { - return; - } - values[groupIndex] = { ...values[groupIndex], values: _updatedValues }; - dispatch(formActions.change('thesauri.data.values', values)); - return; - } - if (areGroupsRemovedFromList(updatedValues, values)) { - return; - } - dispatch(formActions.change('thesauri.data.values', _updatedValues)); - }; -} - -export function addValue(group) { - return (dispatch, getState) => { - const values = getState().thesauri.data.values.slice(0); - if (group !== undefined) { - values[group] = { ...values[group] }; - values[group].values = values[group].values.slice(0); - values[group].values.push({ label: '', id: ID() }); - } else { - values.push({ label: '', id: ID() }); - } - - dispatch(formActions.change('thesauri.data.values', values)); - }; -} - -export function addGroup() { - return (dispatch, getState) => { - const values = getState().thesauri.data.values.slice(0); - const lastIndex = values.length - 1; - const newGroup = { label: '', id: ID(), values: [{ label: '', id: ID() }] }; - if (!values[lastIndex].values) { - values[lastIndex] = newGroup; - } else { - values.push(newGroup); - } - dispatch(formActions.change('thesauri.data.values', values)); - }; -} - -export function removeValue(index, groupIndex) { - return (dispatch, getState) => { - const values = getState().thesauri.data.values.slice(0); - if (typeof groupIndex === 'number') { - values[groupIndex] = { ...values[groupIndex] }; - values[groupIndex].values = values[groupIndex].values.slice(0); - values[groupIndex].values.splice(index, 1); - } else { - values.splice(index, 1); - } - dispatch(formActions.change('thesauri.data.values', values)); - }; -} diff --git a/app/react/Thesauri/actions/thesaurisActions.js b/app/react/Thesauri/actions/thesaurisActions.js index bc1a45cd24..36e65fbb80 100644 --- a/app/react/Thesauri/actions/thesaurisActions.js +++ b/app/react/Thesauri/actions/thesaurisActions.js @@ -1,29 +1,5 @@ import { actions } from 'app/BasicReducer'; -import TemplatesAPI from 'app/Templates/TemplatesAPI'; import api from 'app/Thesauri/ThesauriAPI'; -import { RequestParams } from 'app/utils/RequestParams'; -import { actions as formActions } from 'react-redux-form'; - -export function editThesaurus(thesaurus) { - return dispatch => { - dispatch(formActions.reset('thesauri.data')); - dispatch(formActions.load('thesauri.data', thesaurus)); - }; -} - -export function deleteThesaurus(thesaurus) { - return dispatch => - api.delete(new RequestParams({ _id: thesaurus._id })).then(() => { - dispatch(actions.remove('dictionaries', thesaurus)); - }); -} - -export function checkThesaurusCanBeDeleted(thesaurus) { - return dispatch => - TemplatesAPI.countByThesauri(new RequestParams({ _id: thesaurus._id })).then(count => - count ? Promise.reject() : dispatch - ); -} export function reloadThesauri() { return dispatch => diff --git a/app/react/V2/Components/UI/Button.tsx b/app/react/V2/Components/UI/Button.tsx index 7fa629f61e..e8f7132cac 100644 --- a/app/react/V2/Components/UI/Button.tsx +++ b/app/react/V2/Components/UI/Button.tsx @@ -70,7 +70,7 @@ const Button = ({ borderHover = 'enabled:hover:border-primary-800'; borderDisabled = 'disabled:border-primary-300'; text = 'text-primary-700'; - textDisabled = 'disabled:text-primary-300'; + bgDisabled = 'disabled:text-primary-300'; textHover = 'enabled:hover:text-primary-700'; break; } @@ -80,11 +80,7 @@ const Button = ({ classNames = `bg-white enabled:hover:text-white ${text} ${border} ${textDisabled} ${borderDisabled} ${bgHover} ${borderHover}`; break; case 'light': - classNames = `bg-white text-gray-700 disabled:text-gray-300 bg-white border-gray-200 ${textHover}`; - break; - case 'action': - classNames = - 'bg-white text-xs font-medium text-indigo-800 disabled:text-indigo-300 bg-white ring-indigo-100'; + classNames = `bg-white text-gray-700 disabled:text-gray-300 border-gray-200 ${textHover} enabled:hover:bg-primary-50 ${borderHover}`; break; default: classNames = `text-white ${bgColor} ${border} ${bgDisabled} ${borderDisabled} ${bgHover} ${borderHover}`; @@ -100,7 +96,6 @@ const Button = ({ border focus:outline-none focus:ring-4 focus:ring-indigo-200 `} form={form} data-testid={dataTestid} - style={styling === 'action' ? { borderColor: '#E5EDFF' } : {}} > {children} diff --git a/app/react/V2/Components/UI/TableV2/Table.tsx b/app/react/V2/Components/UI/TableV2/Table.tsx index 84363419f2..e02262f5f7 100644 --- a/app/react/V2/Components/UI/TableV2/Table.tsx +++ b/app/react/V2/Components/UI/TableV2/Table.tsx @@ -195,7 +195,7 @@ const Table = >({ onDragEnd={handleDragEnd} sensors={sensors} > -
+
{header && } diff --git a/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx b/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx index 5cde237df8..2186ae0299 100644 --- a/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx +++ b/app/react/V2/Routes/Settings/ActivityLog/components/TableElements.tsx @@ -101,7 +101,7 @@ const TimeCell = const ViewCell = ({ cell, column }: CellContext) => ( - diff --git a/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx b/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx index 2bcd4c2972..6bbf47d0f7 100644 --- a/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/RelationshipTypes/components/TableComponents.tsx @@ -11,7 +11,7 @@ interface TableRelationshipType extends ClientRelationshipType { const EditButton = ({ cell, column }: CellContext) => ( + Selected {selectedThesaurusValue.length} + of {thesaurusValues.length} + + )} + {selectedThesaurusValue.length === 0 && ( +
+
+ + + + { + setIsImporting(true); + }} + disabled={isEmpty(getValues().name)} + getThesaurus={getCurrentStatus} + onSuccess={(savedThesaurus: ThesaurusSchema) => { + setValue('_id', savedThesaurus._id); + setNotifications({ + type: 'success', + text: Thesauri updated., + }); + navigate(`../edit/${savedThesaurus._id}`); + setIsImporting(false); + }} + onFailure={() => { + setIsImporting(false); + setNotifications({ + type: 'error', + text: Error adding thesauri., + }); + }} + /> +
+ +
+ )} + + + { + setShowThesauriValueFormSidepanel(false); + }} + thesaurusValues={thesaurusValues} + /> + { + setShowThesauriGroupFormSidepanel(false); + }} + /> + {showNavigationModal && ( + + )} + {confirmCallback !== undefined && ( + + )} + + ); +}; +export { EditThesaurus }; diff --git a/app/react/V2/Routes/Settings/Thesauri/ThesauriList.tsx b/app/react/V2/Routes/Settings/Thesauri/ThesauriList.tsx index 821346da40..777def9121 100644 --- a/app/react/V2/Routes/Settings/Thesauri/ThesauriList.tsx +++ b/app/react/V2/Routes/Settings/Thesauri/ThesauriList.tsx @@ -1,41 +1,32 @@ -import React, { useEffect, useState } from 'react'; -import { ColumnDef, Row, createColumnHelper } from '@tanstack/react-table'; +import React, { useMemo, useState } from 'react'; +import { IncomingHttpHeaders } from 'http'; +import { Link, LoaderFunction, useLoaderData, useRevalidator } from 'react-router-dom'; import { useSetAtom, useAtomValue } from 'jotai'; import { Translate } from 'app/I18N'; import ThesauriAPI from 'app/V2/api/thesauri'; import { SettingsContent } from 'app/V2/Components/Layouts/SettingsContent'; -import { Button, Table_deprecated as Table, ConfirmationModal } from 'app/V2/Components/UI'; -import { IncomingHttpHeaders } from 'http'; -import { Link, LoaderFunction, useLoaderData, useNavigate, useRevalidator } from 'react-router-dom'; -import { ThesaurusSchema } from 'shared/types/thesaurusType'; +import { Button, ConfirmationModal } from 'app/V2/Components/UI'; import { notificationAtom, templatesAtom } from 'app/V2/atoms'; import { ClientThesaurus, Template } from 'app/apiResponseTypes'; -import { - EditButton, - LabelHeader, - ActionHeader, - TemplateHeader, - ThesaurusLabel, - templatesCells, -} from './components/TableComponents'; +import { ThesauriTable } from './components/ThesauriTable'; +import type { ThesauriRow } from './components/ThesauriTable'; -const theasauriListLoader = +const thesauriLoader = (headers?: IncomingHttpHeaders): LoaderFunction => async () => ThesauriAPI.getThesauri({}, headers); const ThesauriList = () => { - const navigate = useNavigate(); const revalidator = useRevalidator(); const thesauri = useLoaderData() as ClientThesaurus[]; const setNotifications = useSetAtom(notificationAtom); const templates = useAtomValue(templatesAtom); const [showConfirmationModal, setShowConfirmationModal] = useState(false); - const [tableThesauri, setTableThesauri] = useState([]); - const [selectedThesauri, setSelectedThesauri] = useState[]>([]); + const [currentThesauri, setCurrentThesauri] = useState([]); + const [selectedThesauri, setSelectedThesauri] = useState([]); - useEffect(() => { - setTableThesauri( + useMemo(() => { + setCurrentThesauri( thesauri.map(thesaurus => { const templatesUsingIt = templates .map(t => { @@ -45,24 +36,20 @@ const ThesauriList = () => { return usingIt ? t : null; }) .filter(t => t) as Template[]; - return { ...thesaurus, + rowId: thesaurus._id, templates: templatesUsingIt, disableRowSelection: Boolean(templatesUsingIt.length), - }; + } as ThesauriRow; }) ); }, [thesauri, templates]); - const navigateToEditThesaurus = (thesaurus: Row) => { - navigate(`./edit/${thesaurus.original._id}`); - }; - const deleteSelectedThesauri = async () => { try { - const requests = selectedThesauri.map(sThesauri => - ThesauriAPI.delete({ _id: sThesauri.original._id }) + const requests = selectedThesauri.map(thesaurus => + ThesauriAPI.delete({ _id: thesaurus._id.toString() }) ); await Promise.all(requests); setNotifications({ @@ -80,28 +67,6 @@ const ThesauriList = () => { } }; - const columnHelper = createColumnHelper(); - const columns = ({ edit }: { edit: Function }) => [ - columnHelper.accessor('name', { - id: 'name', - header: LabelHeader, - cell: ThesaurusLabel, - meta: { headerClassName: 'w-6/12 font-medium' }, - }) as ColumnDef, - columnHelper.accessor('templates', { - header: TemplateHeader, - cell: templatesCells, - enableSorting: false, - meta: { headerClassName: 'w-6/12' }, - }) as ColumnDef, - columnHelper.accessor('_id', { - header: ActionHeader, - cell: EditButton, - enableSorting: false, - meta: { action: edit, headerClassName: 'w-0 text-center sr-only' }, - }) as ColumnDef, - ]; - return (
{
- - enableSelection - columns={columns({ edit: navigateToEditThesaurus })} - data={tableThesauri} - title={Thesauri} - initialState={{ sorting: [{ id: 'name', desc: false }] }} - onSelection={setSelectedThesauri} +
{selectedThesauri.length ? ( -
+
@@ -155,9 +116,9 @@ const ThesauriList = () => { header={Delete} warningText={Are you sure you want to delete this item?} body={ -
    +
      {selectedThesauri.map(item => ( -
    • {item.original.name}
    • +
    • {item.name}
    • ))}
    } @@ -170,4 +131,4 @@ const ThesauriList = () => { ); }; -export { ThesauriList, theasauriListLoader }; +export { ThesauriList, thesauriLoader }; diff --git a/app/react/V2/Routes/Settings/Thesauri/ThesaurusForm.tsx b/app/react/V2/Routes/Settings/Thesauri/ThesaurusForm.tsx index c1733a7245..6ebf60c14c 100644 --- a/app/react/V2/Routes/Settings/Thesauri/ThesaurusForm.tsx +++ b/app/react/V2/Routes/Settings/Thesauri/ThesaurusForm.tsx @@ -1,212 +1,79 @@ -/* eslint-disable max-statements */ -/* eslint-disable max-lines */ -import React, { useEffect, useMemo, useState } from 'react'; -import { Link, LoaderFunction, useBlocker, useLoaderData, useNavigate } from 'react-router-dom'; -import { IncomingHttpHeaders } from 'http'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { useSetAtom } from 'jotai'; +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import { useNavigate, useRevalidator } from 'react-router-dom'; +import { SubmitHandler, UseFormReturn } from 'react-hook-form'; +import { useAtom, useSetAtom } from 'jotai'; +import { isEqual, remove } from 'lodash'; import { Row } from '@tanstack/react-table'; import { Translate } from 'app/I18N'; -import { SettingsContent } from 'V2/Components/Layouts/SettingsContent'; -import { Button, Table_deprecated as Table } from 'V2/Components/UI'; -import { ConfirmNavigationModal, InputField } from 'V2/Components/Forms'; -import { ClientThesaurusValue, ClientThesaurus } from 'app/apiResponseTypes'; -import { notificationAtom } from 'V2/atoms/notificationAtom'; +import { ClientThesaurus } from 'app/apiResponseTypes'; import ThesauriAPI from 'V2/api/thesauri'; -import uniqueID from 'shared/uniqueID'; -import { ThesaurusSchema } from 'shared/types/thesaurusType'; -import { - ThesauriValueFormSidepanel, - FormThesauriValue, -} from './components/ThesauriValueFormSidepanel'; -import { ThesauriGroupFormSidepanel } from './components/ThesauriGroupFormSidepanel'; -import { sanitizeThesaurusValues } from './helpers'; -import { ImportButton } from './components/ImportButton'; -import { columns, TableThesaurusValue } from './components/TableComponents'; - -const editTheasaurusLoader = - (headers?: IncomingHttpHeaders): LoaderFunction => - async ({ params: { _id } }) => { - const response = await ThesauriAPI.getThesauri({ _id }, headers); - return response[0]; - }; - -const ThesaurusForm = () => { - const navigate = useNavigate(); - const thesaurus = useLoaderData() as ClientThesaurus; - const [showNavigationModal, setShowNavigationModal] = useState(false); - const [groupFormValues, setGroupFormValues] = useState(); - const [itemFormValues, setItemFormValues] = useState([]); - const [showThesauriValueFormSidepanel, setShowThesauriValueFormSidepanel] = useState(false); - const [showThesauriGroupFormSidepanel, setShowThesauriGroupFormSidepanel] = useState(false); - const [selectedThesaurusValue, setSelectedThesaurusValue] = useState[]>( - [] - ); - const [thesaurusValues, setThesaurusValues] = useState([]); - const setNotifications = useSetAtom(notificationAtom); - - useEffect(() => { - if (thesaurus) { - const values = thesaurus.values || []; - const valuesWithIds = values.map((value: ClientThesaurusValue) => ({ - ...value, - _id: `temp_${uniqueID()}`, - values: value.values?.map(val => ({ - ...val, - _id: `temp_${uniqueID()}`, - })), - })); - setThesaurusValues(valuesWithIds); - } - }, [thesaurus]); - +import { thesauriAtom, notificationAtom } from 'app/V2/atoms'; +import { Table } from 'V2/Components/UI'; +import { InputField } from 'V2/Components/Forms'; +import { addSelection, sanitizeThesaurusValues } from './helpers'; +import { columnsThesaurus, ThesaurusRow } from './components/TableComponents'; + +interface ThesaurusFormProps { + thesaurus: ClientThesaurus; + thesaurusValues: ThesaurusRow[]; + form: UseFormReturn; + edit: (row: Row) => void; + setThesaurusValues: React.Dispatch>; + setSelectedThesaurusValue: React.Dispatch>; +} + +const ThesaurusForm = ({ + thesaurus, + thesaurusValues, + form, + edit, + setThesaurusValues, + setSelectedThesaurusValue, +}: ThesaurusFormProps) => { const { - watch, register, - getValues, + setValue, handleSubmit, - formState: { errors, isDirty }, - } = useForm({ - defaultValues: thesaurus, - mode: 'onSubmit', - }); - - const blocker = useBlocker(({ nextLocation }) => { - const nameHasChanged = isDirty; - const hasSaved = nextLocation.pathname.includes('edit'); - if (hasSaved) return false; - if (thesaurus) { - // We are editing - const valuesHaveChanged = thesaurusValues.length !== thesaurus.values.length; - return valuesHaveChanged || thesaurus.name !== getValues().name; - } - const newValues = Boolean(thesaurusValues.length); - return newValues || nameHasChanged; - }); + formState: { errors }, + } = form; - useMemo(() => { - if (blocker.state === 'blocked') { - setShowNavigationModal(true); - } - }, [blocker, setShowNavigationModal]); - - const addItem = () => { - setItemFormValues([]); - setShowThesauriValueFormSidepanel(true); - }; - - const addGroup = () => { - setGroupFormValues({ - _id: `temp_${uniqueID()}`, - label: '', - values: [{ label: '', _id: `temp_${uniqueID()}` }], - }); - setShowThesauriGroupFormSidepanel(true); - }; - - const editGroup = (row: Row) => { - setGroupFormValues(row.original); - setShowThesauriGroupFormSidepanel(true); - }; - - const editValue = (row: Row) => { - setItemFormValues([{ ...row.original, groupId: row.getParentRow()?.original._id }]); - setShowThesauriValueFormSidepanel(true); - }; + const navigate = useNavigate(); + const revalidator = useRevalidator(); + const [thesauri, setThesauri] = useAtom(thesauriAtom); + const setNotifications = useSetAtom(notificationAtom); - const edit = (row: Row) => { - if (row.original.values) { - editGroup(row); + const handleRevalidate = (savedThesaurus: ClientThesaurus) => { + if (!thesaurus?._id) { + navigate(`../edit/${savedThesaurus._id}`); } else { - editValue(row); + revalidator.revalidate(); } }; - const deleteSelected = () => { - const parentsDeleted = thesaurusValues.filter( - currentValue => - !selectedThesaurusValue.find(selected => selected.original._id === currentValue._id) - ); - - const childrenDeleted = parentsDeleted.map(singleThesaurus => { - if (singleThesaurus.values) { - const newValues = singleThesaurus.values?.filter( - currentGroupItem => - !selectedThesaurusValue.find(selected => selected.original._id === currentGroupItem._id) - ); - singleThesaurus.values = newValues; - } - return singleThesaurus; - }); - - setThesaurusValues(childrenDeleted); - }; - - const sortValues = () => { - const valuesCopy = [...thesaurusValues]; - valuesCopy.sort((first, second) => (first.label > second.label ? 1 : -1)); - setThesaurusValues(valuesCopy); - }; - - const addItemSubmit = (items: FormThesauriValue[]) => { - const addingNewItems = !items[0]._id; - const thesaurusValuesCopy = [...thesaurusValues]; - if (addingNewItems) { - items.forEach(item => { - const itemWithId = { ...item, _id: `temp_${uniqueID()}` }; - if (!itemWithId.groupId) { - thesaurusValuesCopy.push(itemWithId); - return; - } - - const group = thesaurusValuesCopy.find(groupItem => groupItem._id === itemWithId.groupId); - if (group) { - group.values = group.values || []; - group.values.push(itemWithId); - } - }); - - setThesaurusValues(thesaurusValuesCopy); - return; + const saveThesaurus = async (data: ClientThesaurus) => { + const thesaurusToUpdate = { ...data, values: sanitizeThesaurusValues(thesaurusValues) }; + const savedThesaurus = await ThesauriAPI.save(thesaurusToUpdate); + if (thesauri.find(item => item._id === savedThesaurus._id)) { + remove(thesauri, item => item._id === savedThesaurus._id); } - - items.forEach(item => { - if (!item.groupId) { - const index = thesaurusValuesCopy.findIndex(tv => tv._id === item._id); - thesaurusValuesCopy[index] = item; - return; - } - - const group = thesaurusValuesCopy.find(groupItem => groupItem._id === item.groupId); - if (group) { - const index = group.values?.findIndex(tv => tv._id === item._id); - if (index !== undefined && index !== -1) { - group.values = group.values || []; - group.values[index] = item; - } - } + thesauri.push(savedThesaurus); + setThesauri([...thesauri]); + setValue('_id', savedThesaurus._id); + setNotifications({ + type: 'success', + text: thesaurus ? ( + Thesauri updated. + ) : ( + Thesauri added. + ), }); - setThesaurusValues(thesaurusValuesCopy); - }; - - const addGroupSubmit = (group: FormThesauriValue) => { - const thesaurusValuesCopy = thesaurusValues.filter(item => item._id !== group._id); - setThesaurusValues([...thesaurusValuesCopy, group]); + handleRevalidate(savedThesaurus); }; const formSubmit: SubmitHandler = async data => { - const sanitizedThesaurus = sanitizeThesaurusValues(data, thesaurusValues); try { - const savedThesaurus = await ThesauriAPI.save(sanitizedThesaurus); - setNotifications({ - type: 'success', - text: thesaurus ? ( - Thesauri updated. - ) : ( - Thesauri added. - ), - }); - navigate(`../edit/${savedThesaurus._id}`); + await saveThesaurus(data); } catch (e) { setNotifications({ type: 'error', @@ -216,124 +83,40 @@ const ThesaurusForm = () => { }; return ( -
    - - +
    +
    + {}} + id="thesauri-name" + placeholder="Thesauri name" + className="mb-2" + hasErrors={!!errors.name} + {...register('name', { required: true })} + /> +
    +
{header}
{ + setSelectedThesaurusValue(() => { + const selection: ThesaurusRow[] = []; + thesaurusValues.forEach(item => { + addSelection(selectedRows, selection)(item); + item.subRows?.forEach(addSelection(selectedRows, selection)); + }); + return [...selection]; + }); + if (!isEqual(rows, thesaurusValues)) { + setThesaurusValues(rows); + } + }} /> - -
-
-
- {}} - id="thesauri-name" - placeholder="Thesauri name" - className="mb-2" - hasErrors={!!errors.name} - {...register('name', { required: true })} - /> -
- - draggableRows - enableSelection - subRowsKey="values" - onChange={setThesaurusValues} - columns={columns({ edit })} - data={thesaurusValues} - initialState={{ sorting: [{ id: 'label', desc: false }] }} - onSelection={setSelectedThesaurusValue} - allowEditGroupsWithDnD={false} - /> -
- -
- - {selectedThesaurusValue.length ? ( -
- - Selected {selectedThesaurusValue.length}{' '} - of {thesaurusValues.length} -
- ) : ( -
-
- - - - { - setNotifications({ - type: 'success', - text: Thesauri updated., - }); - navigate(`../edit/${savedThesaurus._id}`); - }} - onFailure={() => { - setNotifications({ - type: 'error', - text: Error adding thesauri., - }); - }} - /> -
-
- - - - -
-
- )} -
- - { - setShowThesauriValueFormSidepanel(false); - }} - groups={thesaurusValues.filter((value: ClientThesaurusValue) => - Array.isArray(value.values) - )} - /> - { - setShowThesauriGroupFormSidepanel(false); - }} - /> - {showNavigationModal && ( - - )} - + + ); }; -export { ThesaurusForm, editTheasaurusLoader }; +export { ThesaurusForm }; diff --git a/app/react/V2/Routes/Settings/Thesauri/components/ImportButton.tsx b/app/react/V2/Routes/Settings/Thesauri/components/ImportButton.tsx index 42f2adcacd..fe970a5c39 100644 --- a/app/react/V2/Routes/Settings/Thesauri/components/ImportButton.tsx +++ b/app/react/V2/Routes/Settings/Thesauri/components/ImportButton.tsx @@ -1,35 +1,45 @@ import React, { ChangeEventHandler } from 'react'; import { Translate } from 'app/I18N'; +import { ClientThesaurus } from 'app/apiResponseTypes'; import { Button } from 'app/V2/Components/UI'; import ThesauriAPI from 'app/V2/api/thesauri'; -import { ClientThesaurus } from 'app/apiResponseTypes'; -import { sanitizeThesaurusValues } from '../helpers'; const ImportButton = ({ onSuccess, onFailure, - thesaurus, + getThesaurus, + onClick, + disabled, }: { onSuccess: Function; onFailure: Function; - thesaurus: ClientThesaurus; + getThesaurus: () => ClientThesaurus; + onClick: Function; + disabled: Boolean; }) => { const importThesauri: ChangeEventHandler = async e => { if (e.target.files && e.target.files[0]) { try { - const sanitizedThesaurus = sanitizeThesaurusValues(thesaurus, thesaurus.values); - const data = await ThesauriAPI.importThesaurus(sanitizedThesaurus, e.target.files[0]); + const thesaurus = getThesaurus(); + const data = await ThesauriAPI.importThesaurus(thesaurus, e.target.files[0]); onSuccess(data); } catch (ex) { onFailure(ex); } + } else { + onFailure(); } + document.querySelector('input#import')!.setAttribute('value', ''); }; return ( diff --git a/app/react/V2/Routes/Settings/Thesauri/components/ThesauriTable.tsx b/app/react/V2/Routes/Settings/Thesauri/components/ThesauriTable.tsx new file mode 100644 index 0000000000..6a2323f802 --- /dev/null +++ b/app/react/V2/Routes/Settings/Thesauri/components/ThesauriTable.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Row } from '@tanstack/react-table'; +import { Translate } from 'app/I18N'; +import { Table } from 'app/V2/Components/UI'; +import { ClientThesaurus, Template } from 'app/apiResponseTypes'; +import { ObjectIdSchema } from 'shared/types/commonTypes'; +import { columnsThesauri } from './TableComponents'; + +interface ThesauriRow extends ClientThesaurus { + _id: ObjectIdSchema; + rowId: string; + disableRowSelection?: boolean; + templates: Template[]; +} + +interface ThesauriTableProps { + currentThesauri: ThesauriRow[]; + setSelectedThesauri: React.Dispatch>; +} + +const ThesauriTable = ({ currentThesauri, setSelectedThesauri }: ThesauriTableProps) => { + const navigate = useNavigate(); + const navigateToEditThesaurus = (thesaurus: Row) => { + navigate(`./edit/${thesaurus.original._id}`); + }; + + return ( +
{ + setSelectedThesauri(currentThesauri.filter(thesaurus => thesaurus.rowId in selectedRows)); + }} + enableSelections + header={ +
+

+ Thesauri +

+
+ } + /> + ); +}; + +export type { ThesauriRow }; +export { ThesauriTable }; diff --git a/app/react/V2/Routes/Settings/Thesauri/components/ThesauriValueFormSidepanel.tsx b/app/react/V2/Routes/Settings/Thesauri/components/ThesauriValueFormSidepanel.tsx index 09a0a48e15..be3e3fb0a9 100644 --- a/app/react/V2/Routes/Settings/Thesauri/components/ThesauriValueFormSidepanel.tsx +++ b/app/react/V2/Routes/Settings/Thesauri/components/ThesauriValueFormSidepanel.tsx @@ -1,63 +1,57 @@ +/* eslint-disable react/jsx-props-no-spreading */ import React, { useEffect } from 'react'; +import { SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; import CheckCircleIcon from '@heroicons/react/20/solid/CheckCircleIcon'; +import { isEmpty, last } from 'lodash'; import { Translate } from 'app/I18N'; import { InputField, Select } from 'app/V2/Components/Forms'; import { Button, Card, Sidepanel } from 'app/V2/Components/UI'; -import { SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; -import { ClientThesaurusValue } from 'app/apiResponseTypes'; -import { TableThesaurusValue } from './TableComponents'; import uniqueID from 'shared/uniqueID'; - -interface FormThesauriValue extends TableThesaurusValue { - groupId?: string; -} +import { ThesaurusRow } from './TableComponents'; interface ThesauriValueFormSidepanelProps { closePanel: () => void; - value: FormThesauriValue[]; - groups?: FormThesauriValue[]; + value: ThesaurusRow[]; + thesaurusValues?: ThesaurusRow[]; showSidepanel: boolean; - submit: SubmitHandler; + submit: SubmitHandler; } +const emptyRow = () => ({ label: '', rowId: uniqueID() }); + const ThesauriValueFormSidepanel = ({ submit, closePanel, - groups, + thesaurusValues, value, showSidepanel, }: ThesauriValueFormSidepanelProps) => { + const editMode = value.length === 1; + const groups = (thesaurusValues || []).filter(item => item.subRows !== undefined); const { reset, control, register, handleSubmit, watch } = useForm<{ - newValues: FormThesauriValue[]; + newValues: ThesaurusRow[]; }>({ mode: 'onSubmit', - defaultValues: { newValues: value.length ? value : [{ label: '', _id: `temp_${uniqueID()}` }] }, + defaultValues: { newValues: value.length ? value : [emptyRow()] }, }); useEffect(() => { - reset({ newValues: value.length ? value : [{ label: '' }] }); + reset({ newValues: value.length ? value : [emptyRow()] }); }, [reset, value]); - const { append, fields } = useFieldArray({ control, name: 'newValues', keyName: 'tempId' }); + const { append, fields } = useFieldArray({ control, name: 'newValues', keyName: 'rowId' }); useEffect(() => { - // if editing, don't append new fields - if (!value.length) { - const subscription = watch(formData => { - const values = (formData as { newValues: FormThesauriValue[] }).newValues - ? (formData as { newValues: FormThesauriValue[] }).newValues - : [formData]; - // @ts-ignore - if (values[values.length - 1].label !== '') { - // @ts-ignore - append({ label: '' }, { shouldFocus: false }); - } - }); - return () => subscription.unsubscribe(); - } - }, [watch, append, value]); + const subscription = watch(formData => { + const values = formData.newValues; + if (!editMode && !isEmpty(last(values)?.label)) { + append(emptyRow(), { shouldFocus: false }); + } + }); + return () => subscription.unsubscribe(); + }, [editMode, watch, append, value]); - const submitHandler = (data: { newValues: FormThesauriValue[] }) => { + const submitHandler = (data: { newValues: ThesaurusRow[] }) => { submit(data.newValues.filter(thesaurus => thesaurus.label !== '')); closePanel(); }; @@ -67,12 +61,13 @@ const ThesauriValueFormSidepanel = ({ isOpen={showSidepanel} withOverlay closeSidepanelFunction={closePanel} - title={value.length > 0 ? Edit item : Add item} + title={editMode ? Edit item : Add item} > {value.length === 0 && ( @@ -94,30 +89,27 @@ const ThesauriValueFormSidepanel = ({ )} {fields.map((localValue, index) => ( - Item} key={localValue.tempId}> + Item} key={localValue.rowId}>
Title} - // eslint-disable-next-line react/jsx-props-no-spreading {...register(`newValues.${index}.label`)} - // onBlur={e => setTyping(e.target.value)} /> {groups && (
+ + + + + + + + + + + + + + + + + + + , +] +`; + +exports[`Settings Thesauri ThesauriList Thesaurus deletion should show the selected thesaurus 1`] = ` +
+
+ + + Selected + + + 2 + + + of + + + 3 +
+
+`; + +exports[`Settings Thesauri ThesauriList render existing thesauri should show a list of existing thesauri 1`] = ` +
+
+
+
+ + + + + Navigate back + + + + +
+
+
+
+
+ + +
+ + Animals + +
+ + Animals + +
+
+
+
+
+ +
+ + +
+ + Colors + +
+ + Colors + +
+
+
+
+
+ +
+ + +
+ + Names + +
+ + Names + +
+
+
+
+
+ + + Document + + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ + Thesauri + +

+
+
+ + + + + + + Label + + + + + + + Templates + + + + + + Action + + +
+ + +
+ + Animals + +
+ + Animals + +
+
+
+
+
+ +
+ + +
+ + Colors + +
+ + Colors + +
+
+
+
+
+ +
+ + +
+ + Names + +
+ + Names + +
+
+
+
+
+ + + Document + + +
+
+
+ +
+ + +
+
+ + + + + +`; diff --git a/app/react/V2/Routes/Settings/Thesauri/specs/fixtures.ts b/app/react/V2/Routes/Settings/Thesauri/specs/fixtures.ts new file mode 100644 index 0000000000..b2ff5fff82 --- /dev/null +++ b/app/react/V2/Routes/Settings/Thesauri/specs/fixtures.ts @@ -0,0 +1,46 @@ +const thesauri = [ + { + _id: 'thesaurus1', + name: 'Colors', + values: [{ _id: 't1_black', label: 'black', id: 'id1_black' }], + }, + { + _id: 'thesaurus2', + name: 'Names', + }, + { + _id: 'thesaurus3', + name: 'Animals', + }, +]; + +const savedThesaurus = { + _id: 'newThesaurus1', + name: 'new thesaurus', + values: [ + { + id: 'item1', + label: 'single value 1', + }, + { + id: 'item2', + label: 'single value 2', + }, + { + id: 'item3', + label: 'Group 1', + values: [ + { + id: 'item3-1', + label: 'Child 1', + }, + { + id: 'item3-2', + label: 'new child 2', + }, + ], + }, + ], +}; + +export { thesauri, savedThesaurus }; diff --git a/app/react/V2/Routes/Settings/Thesauri/specs/helpers.spec.ts b/app/react/V2/Routes/Settings/Thesauri/specs/helpers.spec.ts new file mode 100644 index 0000000000..7bbac44559 --- /dev/null +++ b/app/react/V2/Routes/Settings/Thesauri/specs/helpers.spec.ts @@ -0,0 +1,466 @@ +import { ThesaurusRow } from '../components'; +import { + addGroupSubmit, + addItemSubmit, + addSelection, + compareThesaurus, + removeItem, + sanitizeThesauri, + sanitizeThesaurusValues, + sortValues, + thesaurusAsRow, +} from '../helpers'; + +const thesaurusValues: ThesaurusRow[] = [ + { + _id: 'originalId1', + rowId: 'item1', + subRows: [ + { + rowId: 'subItem1-1', + label: 'SubItem1-1', + groupId: 'item1', + //@ts-ignore Keeping _id to ensure legacy compatibility. See #4053 + _id: 'temporalId1', + }, + { + rowId: 'subItem1-2', + label: 'SubItem1-2', + }, + ], + label: 'Item1', + values: [{ id: 'oldItem', label: 'OldItem' }], + }, + { + rowId: 'item2', + id: 'originalId2', + subRows: [ + { + rowId: 'subItem2-1', + label: 'SubItem2-1', + id: 'originalId2', + }, + { + rowId: 'subItem2-2', + label: 'SubItem2-2', + groupId: 'item2', + }, + ], + label: 'Item2', + //@ts-ignore, reusing type, it is expected that the original object to contain values + values: [], + }, +]; + +const sanitizedThesaurusValues: {}[] = [ + { + _id: 'originalId1', + values: [ + { + label: 'SubItem1-1', + _id: 'temporalId1', + }, + { + label: 'SubItem1-2', + }, + ], + label: 'Item1', + }, + { + id: 'originalId2', + values: [ + { + label: 'SubItem2-1', + id: 'originalId2', + }, + { + label: 'SubItem2-2', + }, + ], + label: 'Item2', + }, +]; + +describe('sanitizeThesaurusValues', () => { + it('should remove table properties', () => { + const result = sanitizeThesaurusValues(thesaurusValues as ThesaurusRow[]); + expect(result).toEqual(sanitizedThesaurusValues); + }); +}); + +describe('addSelection', () => { + it('should push the item into selection if rowId is selected', () => { + const selectedRows = { item2: true }; + const selection: ThesaurusRow[] = []; + addSelection(selectedRows, selection)(thesaurusValues[1]); + expect(selection).toHaveLength(1); + expect(selection[0]).toEqual(thesaurusValues[1]); + }); + + it('should not push the item into selection if rowId is not selected ', () => { + const selectedRows = { item1: false }; + const selection: ThesaurusRow[] = []; + addSelection(selectedRows, selection)(thesaurusValues[1]); + expect(selection).toHaveLength(0); + }); +}); + +describe('sanitizeThesauri', () => { + it('should remove empty labels', () => { + const thesaurus = { + _id: 'thesaurus1', + name: 'Thesaurus1', + values: [ + { rowId: 'newId', label: '' }, + { + rowId: 'value1', + label: 'Value1', + values: [ + { + id: 'value 1-1', + _id: 'value 1-1', + label: 'Value 1-1', + }, + { + id: 'other', + label: '', + }, + ], + }, + ], + }; + const sanitizedThesaurus = { + _id: 'thesaurus1', + name: 'Thesaurus1', + values: [ + { + rowId: 'value1', + label: 'Value1', + values: [ + { + _id: 'value 1-1', + id: 'value 1-1', + label: 'Value 1-1', + }, + ], + }, + ], + }; + const result = sanitizeThesauri(thesaurus); + expect(result).toEqual(sanitizedThesaurus); + }); +}); + +describe('thesaurusAsRow', () => { + it('should append a rowId with id for existent items and a new id for the new ones', () => { + const thesaurus = { + _id: 'old_Id', + id: 'oldId', + label: 'label1', + values: [ + { + id: 'item1-1', + label: 'Item1-1', + }, + { + label: 'new1-2', + }, + ], + }; + const result = thesaurusAsRow(thesaurus); + expect(result).toEqual({ + _id: 'old_Id', + id: 'oldId', + rowId: 'oldId', + label: 'label1', + subRows: [ + { + id: 'item1-1', + label: 'Item1-1', + rowId: 'item1-1', + groupId: 'oldId', + }, + { + rowId: expect.any(String), + label: 'new1-2', + groupId: 'oldId', + }, + ], + }); + }); + + it('should accept an empty thesaurus', () => { + const thesaurus = { + label: 'label1', + }; + const result = thesaurusAsRow(thesaurus); + expect(result).toEqual({ + rowId: expect.any(String), + label: 'label1', + }); + }); +}); + +describe('removeItem', () => { + let prev: ThesaurusRow[]; + + beforeEach(() => { + prev = [ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [ + { rowId: 'prevChild1', label: 'Prev Child 1' }, + { rowId: 'prevChild2', label: 'Prev Child 2' }, + ], + }, + { rowId: 'prevItem2', label: 'Prev Item 2' }, + { rowId: 'prevItem3', label: 'Prev Item 3' }, + ]; + }); + + it('should delete a root item', () => { + const prevCopy = prev; + removeItem(prevCopy, { rowId: 'prevItem2', label: 'Prev Item 2' }); + expect(prevCopy).toEqual([ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [ + { rowId: 'prevChild1', label: 'Prev Child 1' }, + { rowId: 'prevChild2', label: 'Prev Child 2' }, + ], + }, + { rowId: 'prevItem3', label: 'Prev Item 3' }, + ]); + }); + + it('should delete a child item', () => { + const prevCopy = prev; + removeItem(prevCopy, { rowId: 'prevChild1', label: 'Prev Child 1' }); + expect(prevCopy).toEqual([ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [{ rowId: 'prevChild2', label: 'Prev Child 2' }], + }, + { rowId: 'prevItem2', label: 'Prev Item 2' }, + { rowId: 'prevItem3', label: 'Prev Item 3' }, + ]); + }); + + it('should delete a group if all the items are deleted', () => { + const prevCopy = prev; + removeItem(prevCopy, { rowId: 'prevChild1', label: 'Prev Child 1' }); + removeItem(prevCopy, { rowId: 'prevChild2', label: 'Prev Child 2' }); + expect(prevCopy).toEqual([ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { rowId: 'prevItem2', label: 'Prev Item 2' }, + { rowId: 'prevItem3', label: 'Prev Item 3' }, + ]); + }); +}); + +describe('sortValues', () => { + it('should sort alphabetically the thesaurus values', () => { + const setThesaurusValues = jest.fn(); + const valuesToSort = [ + { rowId: 't1', label: 'Last item' }, + { + rowId: 't2', + label: 'Group', + subRows: [ + { rowId: 'c1', label: 'Last Child' }, + { rowId: 'c2', label: 'First Child' }, + ], + }, + { rowId: 't3', label: 'First item' }, + ]; + sortValues(valuesToSort, setThesaurusValues)(); + expect(setThesaurusValues).toHaveBeenCalledWith([ + { rowId: 't3', label: 'First item' }, + { + rowId: 't2', + label: 'Group', + subRows: [ + { rowId: 'c2', label: 'First Child' }, + { rowId: 'c1', label: 'Last Child' }, + ], + }, + { rowId: 't1', label: 'Last item' }, + ]); + }); +}); + +describe('addItemSubmit', () => { + let prev: ThesaurusRow[]; + + beforeEach(() => { + prev = [ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [{ rowId: 'prevChild1', label: 'Prev Child 1' }], + }, + ]; + }); + + let result: ThesaurusRow[]; + + it('should add new root items', () => { + const items = [ + { rowId: 'newItem2', label: 'New Item2' }, + { rowId: 'newItem3', label: 'New Item3' }, + ]; + const setThesaurusValues = jest.fn().mockImplementation(fn => { + result = fn(prev); + }); + addItemSubmit(prev, setThesaurusValues)(items); + expect(result).toEqual([ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [{ rowId: 'prevChild1', label: 'Prev Child 1' }], + }, + { rowId: 'newItem2', label: 'New Item2' }, + { rowId: 'newItem3', label: 'New Item3' }, + ]); + }); + it('should edit a root item', () => { + const items = [{ rowId: 'prevItem1', label: 'Changed Item 1' }]; + const setThesaurusValues = jest.fn(); + addItemSubmit(prev, setThesaurusValues)(items); + expect(setThesaurusValues).toHaveBeenCalledWith([ + { rowId: 'prevItem1', label: 'Changed Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [{ rowId: 'prevChild1', label: 'Prev Child 1' }], + }, + ]); + }); + it('should add items into a group', () => { + const items = [{ rowId: 'newItem2', label: 'New Item2', groupId: 'prevGroup1' }]; + const setThesaurusValues = jest.fn().mockImplementation(fn => { + result = fn(prev); + }); + addItemSubmit(prev, setThesaurusValues)(items); + expect(result).toEqual([ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [ + { rowId: 'prevChild1', label: 'Prev Child 1' }, + { rowId: 'newItem2', label: 'New Item2' }, + ], + }, + ]); + }); +}); +describe('addGroupSubmit', () => { + let prev: ThesaurusRow[]; + + beforeEach(() => { + prev = [ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [{ rowId: 'prevChild1', label: 'Prev Child 1' }], + }, + ]; + }); + let result: ThesaurusRow[]; + const setThesaurusValues = jest.fn().mockImplementation(fn => { + result = fn(prev); + }); + + it('should add a new group', () => { + const group = { + rowId: 'newGroup2', + label: 'New Group2', + subRows: [{ rowId: 'newChild2', label: 'New Child 2', groupId: 'newGroup2' }], + }; + addGroupSubmit(prev, setThesaurusValues)(group); + expect(result).toEqual([ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Prev Group1', + subRows: [{ rowId: 'prevChild1', label: 'Prev Child 1' }], + }, + { + rowId: 'newGroup2', + label: 'New Group2', + subRows: [{ rowId: 'newChild2', label: 'New Child 2' }], + }, + ]); + }); + it('should add items to an existent group', () => { + const group = { + rowId: 'prevGroup1', + label: 'Updated Group1', + subRows: [ + { rowId: 'prevChild1', label: 'Prev Child 1', groupId: 'prevGroup1' }, + { rowId: 'newChild1', label: 'New Child 1', groupId: 'prevGroup1' }, + ], + }; + addGroupSubmit(prev, setThesaurusValues)(group); + expect(result).toEqual([ + { rowId: 'prevItem1', label: 'Prev Item 1' }, + { + rowId: 'prevGroup1', + label: 'Updated Group1', + subRows: [ + { rowId: 'prevChild1', label: 'Prev Child 1' }, + { rowId: 'newChild1', label: 'New Child 1' }, + ], + }, + ]); + }); +}); + +describe('compareThesaurus', () => { + const originalThesaurus = { + _id: 't1', + name: 'Original Name', + values: [{ id: 'v1', label: 'Value 1' }], + }; + it('should return true if the values are the same', () => { + const result = compareThesaurus(originalThesaurus, { ...originalThesaurus }); + expect(result).toEqual(false); + }); + it('should return false if the name has changed', () => { + const newThesaurus = { + _id: 't1', + name: 'New Name', + values: [{ id: 'v1', label: 'Value 1' }], + }; + const result = compareThesaurus(originalThesaurus, newThesaurus); + expect(result).toEqual(true); + }); + it('should return false if a value has changed', () => { + const newThesaurus = { + _id: 't1', + name: 'Original Name', + values: [{ id: 'v1', label: 'New Value 1' }], + }; + const result = compareThesaurus(originalThesaurus, newThesaurus); + expect(result).toEqual(true); + }); + it('should return false if there is a new value', () => { + const newThesaurus = { + _id: 't1', + name: 'Original Name', + values: [{ id: 'v1', label: 'Value 1' }, { label: 'Value 2' }], + }; + const result = compareThesaurus(originalThesaurus, newThesaurus); + expect(result).toEqual(true); + }); +}); diff --git a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx index 395b4bc4b5..6e3600c9b6 100644 --- a/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Translations/components/TableComponents.tsx @@ -12,7 +12,7 @@ const ActionHeader = () => Action; const RenderButton = ({ cell }: CellContext) => ( - diff --git a/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx b/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx index d37da40c6b..1a1b4fc1d5 100644 --- a/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx +++ b/app/react/V2/Routes/Settings/Users/components/TableComponents.tsx @@ -84,7 +84,7 @@ const UsernameCell = ({ cell }: CellContext ( - ); diff --git a/app/react/V2/api/thesauri/index.ts b/app/react/V2/api/thesauri/index.ts index a195151baf..e7f97eaaab 100644 --- a/app/react/V2/api/thesauri/index.ts +++ b/app/react/V2/api/thesauri/index.ts @@ -11,13 +11,13 @@ export default { return api.get(url, requestParams).then((response: any) => response.json.rows); }, - save(thesaurus: ThesaurusSchema, headers?: IncomingHttpHeaders) { - const requestParams = new RequestParams(thesaurus, headers); + save(thesaurus: ThesaurusSchema) { + const requestParams = new RequestParams(thesaurus); return api.post('thesauris', requestParams).then((response: any) => response.json); }, - delete(params: { _id: string }, headers?: IncomingHttpHeaders) { - const requestParams = new RequestParams(params, headers); + delete(params: { _id: string }) { + const requestParams = new RequestParams(params); return api.delete('thesauris', requestParams).then((response: any) => response.json); }, diff --git a/app/react/V2/atoms/store.ts b/app/react/V2/atoms/store.ts index 3a505801e4..1ffb4f9b9d 100644 --- a/app/react/V2/atoms/store.ts +++ b/app/react/V2/atoms/store.ts @@ -55,6 +55,10 @@ if (isClient && window.__atomStoreData__) { const value = atomStore.get(relationshipTypesAtom); store?.dispatch({ type: 'relationTypes/SET', value }); }); + atomStore.sub(thesauriAtom, () => { + const value = atomStore.get(thesauriAtom); + store?.dispatch({ type: 'dictionaries/SET', value }); + }); } export type { AtomStoreData }; diff --git a/app/react/V2/shared/testingHelpers.ts b/app/react/V2/shared/testingHelpers.ts index 8689dd6be2..1fc252ce5e 100644 --- a/app/react/V2/shared/testingHelpers.ts +++ b/app/react/V2/shared/testingHelpers.ts @@ -3,8 +3,9 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { createStore } from 'jotai'; import { fromJS } from 'immutable'; -import { IStore } from 'app/istore'; import { merge } from 'lodash'; +import createMockStore from 'redux-mock-store'; +import { IStore } from 'app/istore'; import { ClientSettings } from 'app/apiResponseTypes'; import { settingsAtom } from '../atoms'; @@ -37,4 +38,15 @@ const atomsGlobalState = (initialState: { settings?: ClientSettings } = {}) => { return myStore; }; -export { LEGACY_createStore, atomsGlobalState }; +const reduxStore = createMockStore([thunk])(() => ({ + locale: 'en', + inlineEdit: fromJS({ inlineEdit: true }), + translations: fromJS([ + { + locale: 'en', + contexts: [], + }, + ]), +})); + +export { LEGACY_createStore, atomsGlobalState, reduxStore }; diff --git a/app/react/apiResponseTypes.d.ts b/app/react/apiResponseTypes.d.ts index b5f6d98fe9..4251d44bc1 100644 --- a/app/react/apiResponseTypes.d.ts +++ b/app/react/apiResponseTypes.d.ts @@ -76,14 +76,11 @@ export interface ClientSettings } export interface ClientThesaurus extends ThesaurusSchema, Omit { - _id: string; + _id: ObjectIdSchema; values: ClientThesaurusValue[]; } -export interface ClientThesaurusValue - extends ThesaurusValueSchema, - Omit { - _id: string; +export interface ClientThesaurusValue extends Omit { id?: string; label: string; values?: { diff --git a/contents/ui-translations/ar.csv b/contents/ui-translations/ar.csv index 8e88a93061..be30d3627b 100644 --- a/contents/ui-translations/ar.csv +++ b/contents/ui-translations/ar.csv @@ -117,6 +117,7 @@ Change password,تغيير كلمة المرور Change Password,Change Password Change will take effect after saving the template,سينفذ التغيير بعد حفظ القالب Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,"This option enhances support for non-Latin languages as the default languages of your collection. Selecting this option will update all template properties automatically. The process could take several minutes. Selecting this option will likely change the URLs of library filters. As a consequence, if you have menus or links using such URLs, they will probably stop working. @@ -208,7 +209,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,تحميل مخصص Dashboard,Dashboard -Data imported,البيانات المستوردة Date added,تم إضافة التاريخ Date format,صيغة التاريخ Date modified,تاريخ التعديل diff --git a/contents/ui-translations/en.csv b/contents/ui-translations/en.csv index 538d5f28bf..0ace762af2 100644 --- a/contents/ui-translations/en.csv +++ b/contents/ui-translations/en.csv @@ -120,6 +120,7 @@ Change password,Change password Change Password,Change Password Change will take effect after saving the template,Change will take effect after saving the template Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,"This option enhances support for non-Latin languages as the default languages of your collection. Selecting this option will update all template properties automatically. The process could take several minutes. Selecting this option will likely change the URLs of library filters. As a consequence, if you have menus or links using such URLs, they will probably stop working. @@ -211,7 +212,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,Custom Uploads Dashboard,Dashboard -Data imported,Data imported Date added,Date added Date format,Date format Date modified,Date modified diff --git a/contents/ui-translations/es.csv b/contents/ui-translations/es.csv index d7a3af2d59..c7fcbd3c9d 100644 --- a/contents/ui-translations/es.csv +++ b/contents/ui-translations/es.csv @@ -118,6 +118,7 @@ Change password,Cambiar la contraseña Change Password,Change Password Change will take effect after saving the template,El cambio se realizará después de guardar la plantilla. Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Los cambios no pueden deshacerse después de guardar. Cambiar campos de texto pueden invalidar las sugerencias. +Changes in the thesaurus will impact all the entities using these values.,Los cambios en Tesauros impactarán en todas las entidades que usen esos valores. Changing the type will erase all relationships to this entity.,Cambiar el tipo borrará todas las relaciones de esta entidad. Character support description,"Esta opción mejora la compatibilidad con idiomas no latinos como idiomas predeterminados de tu colección. Al seleccionar esta opción, todas las propiedades de la plantilla se actualizarán automáticamente. El proceso puede tardar varios minutos. Seleccionar esta opción probablemente cambiará las URL de los filtros de la colección. Como consecuencia, si tienes menús o enlaces que usan dichas URL, probablemente dejarán de funcionar. Deberás actualizarlos manualmente. Después de seleccionar esta opción, no podrás volver a usar los nombres de la propiedad heredada. Si no tienes problemas con los nombres de las propiedades de tu plantilla, recomendamos no marcar esta opción." Character support process warning,"Este proceso podría demorar varios minutos y probablemente cambiará las URL a los filtros de la colección. Si tienes menús o enlaces que usan dichas URL, probablemente dejarán de funcionar después de la actualización. Deberás actualizarlos manualmente." @@ -207,7 +208,6 @@ Custom JS,JS Personalizado custom page error warning,"Hay un error inesperado en esta página personalizada, es posible que no funcione correctamente. Ponte en contacto con un administrador para más detalles." Custom Uploads,Subidas personalizadas Dashboard,Tablero -Data imported,Datos importados Date added,Fecha de creación Date format,Formato de fecha Date modified,Fecha de la modificación diff --git a/contents/ui-translations/fr.csv b/contents/ui-translations/fr.csv index 57069b845b..64c7e18394 100644 --- a/contents/ui-translations/fr.csv +++ b/contents/ui-translations/fr.csv @@ -117,6 +117,7 @@ Change password,Changer le mot de passe Change Password,Change Password Change will take effect after saving the template,Le changement prendra effet après l'enregistrement du modèle Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,"This option enhances support for non-Latin languages as the default languages of your collection. Selecting this option will update all template properties automatically. The process could take several minutes. Selecting this option will likely change the URLs of library filters. As a consequence, if you have menus or links using such URLs, they will probably stop working. @@ -208,7 +209,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,Téléchargements personnalisés Dashboard,Dashboard -Data imported,Données importées Date added,Date ajoutée Date format,Format de date Date modified,Date de modification diff --git a/contents/ui-translations/ko.csv b/contents/ui-translations/ko.csv index 0801667513..fedda4e47b 100644 --- a/contents/ui-translations/ko.csv +++ b/contents/ui-translations/ko.csv @@ -120,6 +120,7 @@ Change password,비밀번호 변경 Change Password,Change Password Change will take effect after saving the template,템플릿 저장 후 변경 사항이 적용됩니다. Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,This option enhances support for non-Latin languages as the default languages of your collection. Character support process warning,"This process could take several minutes and will likely change URLs to library filters. If you have menus or links using such URLs, they will probably stop working after the update. You will need to update them manually." @@ -209,7 +210,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,사용자 맞춤 업로드 Dashboard,Dashboard -Data imported,데이터 업로드 완료 Date added,추가한 날짜 Date format,날짜 형식 Date modified,수정한 날짜 diff --git a/contents/ui-translations/my.csv b/contents/ui-translations/my.csv index 8c4fb3ecb8..6fde2e2853 100644 --- a/contents/ui-translations/my.csv +++ b/contents/ui-translations/my.csv @@ -120,6 +120,7 @@ Change password,စကားဝှက် ပြောင်းရန် Change Password,Change Password Change will take effect after saving the template,ပုံစံပြားကို သိမ်းဆည်းပြီးနောက် အပြောင်းအလဲ စတင်သက်ရောက်ပါမည် Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,This option enhances support for non-Latin languages as the default languages of your collection. Character support process warning,"This process could take several minutes and will likely change URLs to library filters. If you have menus or links using such URLs, they will probably stop working after the update. You will need to update them manually." @@ -209,7 +210,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,စိတ်ကြိုက် အပ်လုဒ်များ Dashboard,Dashboard -Data imported,ဒေတာ ထည့်သွင်းပြီး Date added,ဒေတာ ထည့်ပြီး Date format,ရက်စွဲ ဖော်မက် Date modified,ရက်စွဲ ပြင်ဆင်ပြီး diff --git a/contents/ui-translations/ru.csv b/contents/ui-translations/ru.csv index 85a6a93d61..1e0e5a4653 100644 --- a/contents/ui-translations/ru.csv +++ b/contents/ui-translations/ru.csv @@ -117,6 +117,7 @@ Change password,Изменить пароль Change Password,Change Password Change will take effect after saving the template,Изменение вступит в силу после сохранения шаблона Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,This option enhances support for non-Latin languages as the default languages of your collection. Character support process warning,"This process could take several minutes and will likely change URLs to library filters. If you have menus or links using such URLs, they will probably stop working after the update. You will need to update them manually." @@ -206,7 +207,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,Пользовательские загрузки Dashboard,Dashboard -Data imported,Данные импортированы Date added,Дата добавления Date format,Формат даты Date modified,Дата изменена diff --git a/contents/ui-translations/th.csv b/contents/ui-translations/th.csv index a96e811aa1..18043171ea 100644 --- a/contents/ui-translations/th.csv +++ b/contents/ui-translations/th.csv @@ -120,6 +120,7 @@ Change password,เปลี่ยนรหัสผ่าน Change Password,Change Password Change will take effect after saving the template,การเปลี่ยนแปลงจะมีผลหลังจากบันทึกเทมเพลต Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,This option enhances support for non-Latin languages as the default languages of your collection. Character support process warning,"This process could take several minutes and will likely change URLs to library filters. If you have menus or links using such URLs, they will probably stop working after the update. You will need to update them manually." @@ -209,7 +210,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,อัปโหลดแบบกำหนดค่าเอง Dashboard,Dashboard -Data imported,ข้อมูลที่ถูกอิมพอร์ท Date added,เพิ่ม ณ วันที่ Date format,รูปแบบวันที่ Date modified,แก้ไข ณ วันที่ diff --git a/contents/ui-translations/tr.csv b/contents/ui-translations/tr.csv index c6670cc69f..6ce3d9f807 100644 --- a/contents/ui-translations/tr.csv +++ b/contents/ui-translations/tr.csv @@ -120,6 +120,7 @@ Change password,Parola değiştir Change Password,Change Password Change will take effect after saving the template,Değişiklik şablonu kaydettikten sonra yürürlüğe girecek Changes can't be undone after saving. Changing text fields may invalidate the suggestions.,Changes can't be undone after saving. Changing text fields may invalidate the suggestions. +Changes in the thesaurus will impact all the entities using these values.,Changes in the thesaurus will impact all the entities using these values. Changing the type will erase all relationships to this entity.,Changing the type will erase all relationships to this entity. Character support description,This option enhances support for non-Latin languages as the default languages of your collection. Character support process warning,"This process could take several minutes and will likely change URLs to library filters. If you have menus or links using such URLs, they will probably stop working after the update. You will need to update them manually." @@ -209,7 +210,6 @@ Custom JS,Custom JS custom page error warning,"There is an unexpected error on this custom page, it may not work properly. Please contact an admin for details." Custom Uploads,Özel Yüklemeler Dashboard,Dashboard -Data imported,İçe aktarılan veri Date added,Eklenme tarihi Date format,Tarih biçimi Date modified,Değiştirilme tarihi diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should add groups #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should add groups #0.png new file mode 100644 index 0000000000000000000000000000000000000000..eac6a281a4eea6d5afc1eb056ae10e818bb13782 GIT binary patch literal 14271 zcmeHuXH=70x9;9s1%-$OqzHn7^d692EEFjQDbjlt>7jQK0qN3P0FfplHS~^1385%a zdMAMNj&$w{;68hd@4MrkbMClf+#e?-yzd}ut#_5V)_k64P6D68q%RZPB>3ZxKQ7BW zmQecRkMozoeaA&S@SEIUbLEde`2A%h9;mqdx!8Eg198FU__$kSqeFy8h9LpPU5VLz zhyRoy+6U^#QcO}v;IgKyElD)}G)MA$$!!AaidO{h(N$E%7J_K{S4!BYwombV1_yhp zd94y*_GXbCqTa2?VFmF|WFJ-ww*NXTGdae)cKV8V0FgL*`Rm`NJ$*sXpZopw9r8D4 zFG%D4vx3GK@y-g;KH|E1R+8csis|*|Yw%Zpia)yKM~6Ls&g-l=k<i3M%WFvC} zCMRiw2ncKi7Rmh^T~NkQ&{X6cq?0{wr-pi%BlukN*~278RlU6tN9u_Ak8lQpCayv@ zM$)G$3eoP8w-B$(UHJmkw9jhApOfFQw7BCMD}3>~DK2em-(y1IBtvbdMxp%UBYC1!wbv&DZBvI*q=5(Mf`@@;Jp~s| zlCZ0g9STCLTRIgZq&G~&q&^oTRMcUba~Y+zQ7qmRv`uS!@^y#48W7dVdkK8$FOg!Z ze$!Bw?EGpt@evDSYP|;m-PSjZ?o_azIaZW0?sR3M#@9vm!O!cnlwH9s6a!16= z!;?o8U$CGl*cBB2>~z=6S&zE|rD`fv)Un`>UMIkp>-wnAlmH<=HPm$ur^C?l_3t0a z$Ul5n`>cFrtkc$suiW;V3A%Du7@Z($f0LEk1);%{ZP%e_PjQ8Tjs&uEX2f`ecn4>8 zZA4G4psxXwLKh>xOkI)~%^4FN>S7-o>Y~dHW0+l}FR*W)oBrgQ9^fE^R#+Ababauf z_AZMOulp_|l48({c-U24kpjMpY%6^jofe4I({St#%5#?B+U?qq?`>Lv@R3Vv_To z?Mio&>)Ni9JVwekBD~ux?a!vu4NLSkXdRlb>2%E%mhJ1g7DHk`$Q=A(GcE!-+1M&D zb(n2?I_Pw;dINF#4dHFo1G(OdllF1bAU zzqFT+e<*rb>@u*huya&>`ZF8TQpYBU?#`k4Fpl8So$WDER^O~sg442J%HLhb6WcKm zC*w^1tTnV(xsr zzHLABWGF$f`fU_x+?^AX!1o`rvirQ&%GK}xM62?sMQdPaum_Rb zR8H?DL&G8galN9*5%z0!l?QSm_lU1isJd%KBI9bh?#FQ?eRe<2j85~|A%4~&UKU?- zYiQs6xdYFB;X^owPHzL6(<$S;;-8BtQRfpH;y6QJ&kES#Hv{&bl zz%*&0pRO06F7tOAzw1%LZ?Y4xgltZPtF0YUmKARz7XvEiMgX*ys|5lNK zCugd_)aCV_9JV?oeAg^WU)r8=K$&4MbPR}207IyriNPT4AtgTDv zjGWoBu5%wDouShG@?$JM%)qvR%KLq|y_j;-eW&WnC&v_pUd$0ZU#~8t#((c-nY9gV zc3D+qV3&l%k%BR@a57)#{Ft(=ZA9ESEhi&8$JBf#Wq(;_IGAjGWYO5onBiC_Z1Gts%2nC-Wi(E-V+XdwnD@wHuX17u^;f}6&n=9Sca!*k8_ z44n1-ff(~9J@W`@zb1pJC6c&!+y z3+nw{Wcs|MoVQb_c)%6yjdN$A#&#dMlz^cKeh#s;Kd&qA9W`Kdpr#0sc+ zc!`cxyrBDWk3(!5#d6XwV~-^(E2DuQ{Dq72lIp7)b2tX&nF-Sp3S+@8ttC$K6hu&Osc{L-N z*O55-^3>b4*(Pdf=H#%L6`Na|XJ&mw92#(;T!VvuhHXPNMS&j_@jET}rtK?4iivzR ziVU>Kc6Mbosk^?nKUGrZcs-oaMHn>RTL#htE=&)&YokuyCEi{&Oz*em zWzoFf{%@P4M_n)UpZ<^PygUYGBFP+cBlGvOeGL9~wNMCUg1kTKZWKzN9B&`}8`Ahs z+r|S(!4M?@1$OXH7YqyVRS{}3j+D)|6QWw={0)pKVzWQI%wJ}Tl5uwCOrho9N9&V- znFIJ`0(c8>_Zw9Zr}jN^m*$UpAT7-=RuFKmQU5pIX*|U}=TFBP;p;bKGU24*;IPdV zQR?h41sArfW+OUJd^Q!XTGr(k_@0Al&ZxMGv2LB<;)eLHMWT!!-&M}&F|Vq2Uvm|$ z&>_VSLg4@oXj>5AmPRmfpic;Js-LY`W6M&pt(pcvCfAmNBjg!S#>F9IOron?AoA zxcYCq6{QF^84iT{Unh`m`v-apDZXEl9d67bNt*zS0ICaV@^3PjbP!l0sTBMwFU3R> z_LdCnP`m>O38+ZuNB0jP%K+eEvW}vN$|?{iW30gznx&mL*K1K=2kb%k0lA3@L1dV~ReB=cfcqp{)O(%tJa+i|~V6T+GxKgspv-$T*r5q&5y zI(Y{45mfLZK$V(9)jyX5uLm>slf_MEWK_I?>Rrk*KZEWP{-#iZQ`_r)dS;GAk+PWs zqa~E(L+`6_CrU9V*!0WKIftBhc}mmpFeP+Qg!nUz}q(s(&j5{^wx35z3|!BGq|la4Il7`dDaRM+(b6F%e9kW@dTks=)f}2rA*fKBQNE zU0@WgVWt+zZ#3i3b@=mz*Dp&-nw_^ar+u8z_MXZUDV@K7$Fdc(`n}1S2a(*#uI(@Y zw?68En|32wg7B%$dOOUi?7wTb`2PFd+^rtiN~|6m^{tqVan0qghzS5m-03bsDYzg< zUDJFO6Eg|X(E@EKjkjs*HIo0 z&Y2Y(*-vWrvN^Olgj@0C1b!hMD~9nF`lK|xcBMEp2OBummJL=?&MRkCRW7zm_11iA zqWe|;wJ*uEYnZlfb($GqtCQuZ(RvmgXZNExxXoVZ)?#o$Mmz?lA(2@hg1B)-tc7Al z+|MX15$#gwJ!M0lwm0~2-hz+<=4o|jtJE(fQ`CKknR8>&Ej?wQv@7FC=s}?TxII~!LJrfVGiQT0FtpQBrc*Q$1oS{ab zJ~0jk04MitZ$!=;@aJGo@3n_F=PjOpC`c>I3PM~?0)VsKW}U`)M9y8BF{!++c`kSi z9Ea1CW0ij6b{m4T2br4^?yg3iLWxexGZ~VEX?|-no4XHS&T2u~-lcKD0HY7niSqJRRX7ZgBs#aaZWJhPOI<&haK5GO zVlKKdjV>S`bYHf{SlxyXrGiBH#j+{Y!j>J^(0k zjsFmLEV3{QG0{tso#N%UC*qvc>n@n~GaG!Iy&FKtnP=AW=`SMT_ir%WpWYSlcaqhK z_yM%awetMR%3&OouXZ39XR>Y88hlBa1>#bA-XV2IvOf^bK`2YC&bF#fafo?F0VRN5|TCXyIwSJX>|ImS`I?>P= zx|(OfXMh|af36^hP-lD86{2wgE67fHygfC5tvN8nkNH75eS>ISwuS-hse!uED@^pQ z#Smb7t{mkLj1>|QI#0M+4q)vpRM>>9(8D4}Wtu>jSlrp_Iv^h_k2ko@(1skc!rmYm zL@}==Vk?L7*3?hrqX=hYR&urLSfqVh^>VIUf@7OXqSMmM%nF;?#H(HT)ZPX;&iZ!! z(t9!eOL!^oX=OciTYLlG9B>9o#IjSm1G1&8Zk*j_WVRLwe--TEu^eUL;`NM@XO<*# zBb3riY4RS$hcC1Gn50@PxqoPy00PgcsYQPPa+g>`s(pe>;42GDvd2`*Bl1rr!w(>Lt4;SnIY*FB_ z7TEn=sc-zfS5Rp2A)11BX6idL-Zk8wO|-Vqq+) z!eH05SR>wE{hC~X^~tCuq|9tADADrKe<>PvZAeQZ`#yijeI_b{cV~j9502azG{xTft`chm=~i33B}g;>PMu)enS{4WK}$1#;~_P$y{In5Ee` z@^Kd68~;F^Xak|g**x+LISx0ooBmnhhC9K}GS<|a;zZpf3FATlc*jKb z;B*zR`jhO;?`x4ssCVhP%uS)NfEYRbA;$rwCqXSBU^ig&|%_FE2>He_j&#W%sTa@e1i9M8zpc7L-V`9!9a|$W*j8*eaOGx zbiZY!ES62wvu0syvk}~#ni#PRG~tT@zz{?M;Lg~aqOIb^g8wM_y11Ls*WOcIukCdN z${1*N48%=bZN|lmoKe2_Sz@wEYlD|ln2KCpdD>~fWP^g4He&P>om#Y{d4ms-Vb5cW z4Jl8b0j-mlx&s(7F;Hz}&%z3-N;$kkNlsIMQ>o$3t8Ji)f4#M2G8AT-#rGZ8juXCg_G6-*2Wy$I_oB=!x$LBz%(I7Pd)yF)Pf7K)p+5>7?fVX;^@S`?H)jno z5Z?bL-8caX>-YH@?sQDcOD6=J@qI_@V?cTc&@!yaIOsmjX(W=_6B1Y^YM0{fKsLm` zt>15@a*xDzU87XxM|Hw;C3A+?Do`|k{@`Q4=Ya3S$(31`Ylk`lFuq#}clZAJ8QUNF0)PVun5j57J+B#zXqmHyTi2W5(+E`)C=@08Kq(3b8cdCbaj4cM=t-DBdlO;q zpnBog6V&)7q;|okCMH|8w&8we-Srf=9czCiveGY@L#qoX&@Lum)YlrOIuZp#PY4ee zMcUto5sy08IM5kSmX8(CL?Gd5#!la*R#>&Z>gIOE^BW0Gr7eqn*mk6SKez63jorC4 zGg`#anh+k^xPi+>&3#Kjhf1jY4d}HG;B?Y*_K1{ak`zPXK5s`JCyW$rioxL^)3I0g zMD#O(oe|`c2;7^nI%z6eO+awNC-*aaRxD^&M+tflD}5VeZXj$mZ!G98j5jlCJs+cNRkOD1 zNx0XMsP?eyQMrxDMwNA)+d>3(aj+LXGB(!6Op`ZV@oRT?;+X|go`XRp#K|&9Nqup= z1i29JrOqAoAcMXuB*71&iY?m6A+(@)zGX3jV=c2f+R}mD*k9dcxWLBlrrCl3+>6^n z?U(JWLZWRYWYiwcOjo)cN}XR}dNbvERDkN@OqPw+G@~<3SbaO{sb=C-&2FDAM@30W z4u+6vJ-%ksLs(7>d#by%r2O|{Bj*x$-;Z$P1v|C&$61mJ4H56KuvzjQp(tiy6W_ys zK)=q%4LwIpr&ZOxWM8Oq#fB{junPx^Jq6mU`YtkWar9g3<9k+NN{UM}-;oQCv!Nm$ zt$c=_<_x|Pw87P1yB@Qt|1<$WAK+;2CzPx!RufoE`|fza;dcc)yFH2}eGuOQ_VMV%Ww%KGH%JvjsJpdm zY*crA>!Pb8wx-gJvHJ*qFSXw3QWId&#>nxKMeNpgMw{`DaMR0&;WDE+W_{c%J5cy? zts@T%J&xe)YJgKxxq48P>mXylV%&Gy%(9`^OGEt-2WdCqY~{%@;zKQaeZqn-3@C_` z0n=E5Mf?JhJL)QDepkJk_YuSj>k&pmmRS1h8cux8JN@;lJYTO#ySW^siEjQfabbj< z%j&X4x~n15jFGFKZH5HrqM_?Bk(_9~V>l-WC;{Gk8VfVZHJ`N}Eyq%==kL!encjlN zWa(7nqM8~N`eK;ud#gR@0;syx6MCm=pX?8Blv3l?^l777cGcKs@{?2}el2pU*&p2q z9w4h8@gK3K>rJIy$zQR0mn-@pEc;7i&k8jxrFG%1O?SQ*%K~BHFrJ_vbySchKfCZA zIdfB&I5=(JVg_RMp&xQe`zz_BMssMGW_KiYl~8PPdNnCPD|`w$2Y!6LBT?(lC}V$T zmV^$&M95r$cVMxz!Yg3hrqi|66hC*LSt0f-HBZ*m*z(yPy3%VGx(@V){;JwQ#NfOv zgAj27@31u>DMj(A$E)QpLkSDiL#CYyIr%7otqN_o$Yd7BXNB@~B|l_|n8?{rY|B7G zTCE2G-HEcqgKCUM$g{}7_|O?wwN=`mj19I|-BTn=){?LblmTzr2opUJIt7Hz(c(Ru zM|gP6mW3M=tMfQBpB))F_RAjTr3{QE!KBdA-}^B=(sJZ`EAtAc8B@17rlo|!nxh)d za!z#PyRO%dM6nSCgsQW3>Ib^` z*CY?Qq1IxA6CkgGYtEBKuY!=^`W&G}0(b7)=e(UDaCAHS(=f_b%**<+_|7)p{7r2t z!TNYG+=KaV7iYcliIOhASkSH@gy5{y%&R0)l>L$_$P8$tc75jue1KsA3a7OA?$T8Uk>HK6FP*5D~OfF@#`+l{4@$aK{F9Wm@%5!X*FHOZfpLFRG#OR z=PfGDnwv;&kb;0yE;-D=;LqH?e6x|Hq*H42FDWG0<1mZ~^d=#xTJiP|C$EhLSYp|x z(!6s~d1Y#EEV&~ln7nH(n02=~yh>wjso`7I!;LDE|kqff!X)Rca4sCjfRcx61&Z! z<`V$a)(_&?EycMux4Wl{U4R8wObnuTXPj*&B2D_>J%NPeU5sLGc;a=3)YP7n=0wS& zzD1_!qsw$4kVp=KYq%_o6IQ9-Uuc4VFJ>Ai5Wi_9=3Oy2>D|O@!B`|AL;OZlNV>&G zJF@QS0GlS+M|334PQS*m+Ep$c>^7@A!V$q zPZX4PN6CEQ5ver9PwI70RGA!qGbQUf5jcWa|E3w{o|;OdTk8K;xQuH|FuV6sR$+hR z+;KsQd4QXXuyl9fnzi$GWM8F=`MDJ>ZU2)YhosKLs=oNxo?v+vNc0A&9ix-9Rq3@L zb3`irgx$V%4t^Mp!apkYndlHNLhLfe^7K!%#=>`sG9k_86dpehr0|3$Hd*lCl}jGj z&pBsQ4{OAQqi8jNX6v6_)tNV9zJi3J(!3}akw&0>6NBJmPajQs&3Cj5&r;j%tXpR= z{0{(p$ybq_pkk3}q7$IM<_kX!f2F7zuASeXEJl#H6PSN0XyIK7DgTSGYX9w@1Z-Fv zYL9iMQy$q8g?iAyyQHET4fN%9V`W{#^lDCYe}lq$i1F|bwf@2Zmp&fLuTKq5{hl!`;NvKjAk^Im z`3c+jMNVQ0=h$;$iPq`0_(JI#ZbonAH}b~|AEgo2Nb<>wp3aH901@VdnagBi1a1*+ z#|6rhXHqk5{q9d-dcShRvZxgaX+=C62r1%1yZ^(v{|h;A{}b8#e;e8SAI|;X=iJfm zE7qpdp6u8gu8X`d`wi5_iv-8#XJ<~j3G_@c!_lU9|B=~83Zy{WYIc@(kC4|Uf;okV zk{%fgcSaEGY*A+EjDJ2vJXp1rS0X1-9hv1ySGqOz#LCu~NqEvHugaL?y+$dHDpMkQ z6ByRp9pv#0m2KJ&=>&ir!3d1N$dj(6^2PB-W@;16Wjs$A&hb(9TZRyxq!32@+C@|D zVl`|EXF{n-YYR>#P72|^F&9>aJU1U7Sx~FSG-2*85-wT91sz8Q~oi;9ELxZfq@sZn`o78*nnZ@7}Dl=3|^s|LVHN&n`cn`ZJ z1P-ShA+e~J2CMno^Qlz=4%XK|h~nHWkWLjlBOp4$3{r&hkLOJnvqFLHy;rJubePh_ zoNK#UZ8n4pLpt%dG8fpwK@yXoql}=Sg_`v4o*Ex~_E379^&SeB*d%_~sP*Zf^BWdX zi*<}eOwShNy7#ZCif5X@x(+SbTiG|EO8$THQGPL^I9-p$|5VKNKhhM8CkJY>aL#)% zy$1V?pTzcyT_(8`rJ%Q0QH-fV8h9@(4 zEaLAKyha3AkG0P@j(BYK(xC=Q^Wv)O>m!uKG|_e|8yiizy-zjgKEo*%`$;kZ+As$+ z?Xb|0!S}Hof`W&T;!~T^tTI_IAg_H@m8(5&AOSP%dMauq)oQjZscd7eyVnkTVAz|# zJkgcPI{~MtnZ+tey;!0Y)wb)zML@3P^>^Z!0?6j1@{Wlr$Be~I)GznT7(^sDtm&T3 zN#zJBBb7JA*Q&oH1}D|j<}I;lbEVeAK4!&bm4ZmR;;Mc6Hqzz)^f&*gblhPG%>e_SB!FV0VrUhWUW z6eEA0`vE8pAeZgSw>(2sFf&XyeptgqtHT^vnwp#~>3ZW#p^xttR}e0Re0k-k3qWeN zT_Po>q-cGV6egC3xZ>5Xa7aRSl1+MLxAHi}RBCFIhxNlJC#9$Qt2u-(nAz>uhsH{v zi0!hwW}L3cxLirbZa3d&3T5zbCYN12e~U2118km$+#tVYbF1}nHU=o{s%V>@@LkxHNWXylo0yv6NW)I%Tv;T1+@5 z;)4|%7JKE#TcgUQPX_~hz4fCgMM8A^PmsI(+&QQwK(%D`Yi%`ZfAU5s!X+}C3S&UW z8PQgZWNv9aDoykW6XV4u2l)%79+}%mFJYk`v@aR*y)15|bll_NcIvXA^t>vCVheDw}Oh5QLW#y3(z&_!Ihvw@*Kkzl-Id}vZ_d=G~i+MIFx}*z0qBvy@qCm;CS*DulOl|_< z)KynGCf2P;koDOtVMZGjgkBmyKr8&&R<;c_XNFHiv;yu(^^P3K=cCaTwa5quf-`? z2WO0W0Mb$$UR|&AZc-KVG+^g3y>UG>Yj-O678Z1$qN}w#Zvua86sIE4&5|LlpG514 z-kccj{$YT8V3kbh?9=$@*YZq9xN31WePg@3GfzL=m6E4A7K)4aa3{7~x;ad+7BOKA z)o@w~iXsj-+nJI}wp8y!C(z`c*qua+?td-c6eRPV=D$JcPle!!V_{ev>N%UbHfm69 zXt(p6u=!w*T+UeY=!}=9mf`m&oQ49eocUNX@p;qcaDJYJw(Gsel~Kk}^s6Y=ww#1X z(~gvc(1$S$+QDhno;39Eng3MR@$ zvA-(5j?H2)Lnh^U<3ncmT=p#U<3b8{efNCp$WXyyM*vOgL<=ip#9f(4UNL7&>$%dTNpYnK=+w8Tmgx3n{o&BXN;k6wx3not0E~Q_ZbLpuWVu)O(4v~wS zJ)N#j?Z%Hr55_e>t9NCh7{{4^e7sQxK2EvMSS`WlKo6D@Z(x)zZDAgxYZJvdx5a19 zo>yG=EmNBXYOVOjDe&P*)qwk7sK0-_L7lCdx9T1I{w*?Fp{N9&TK!AY!97U?#*l1S z^>%h~PFX^+tzx?8tFE*#w&y2}-bmGJT=h`pFW0V0-myp0&zQ=|C$7uC+TMGU{xhmu zcf6jd`|jJ5GI}ptKE?;kQSJ4~QwO=?{xtXXV$;hBF4M>_cJf!xFRHz4S>CjKCGpCL z^KnDMz%I@z%}EzoH2);hrcH(CTofb;Ml`Yfr%-Sv_K4 zP}kvgVevujw~W!}>r7ixQEushCk3Tt>`a5EfAB@=n`Cwd8v6KNU5%a(Wf%gV>K~L( z%E0HOoDfTK0`GuBA}AU#ur8Y<&nL+jHFH}6*OkZ&#&%Q8-RqZ@*~;QMuy5YKF($d@ z|L4^8R_>(3KYb_OI#oA;rY#vd9|XF{mcHa$AN)b0DY`@3^@{yJ$T zlA4x0RO+N(cb9l?hF-+;{qv-NjPPZ=bFZw+gHMX}z(%a&^vT12{@On+Yc;p!hFc)N zI~EqVPFs>nzh;q4WI%q+&!FMWn|e+5R}zmQkN~<#0jZM~Z+Skrde#5ZU-}aALNN@G db4Pexx9?YCsJI`3f6V(sMiM5G|4{$c{{oX|)$9NO literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should add items #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should add items #0.png new file mode 100644 index 0000000000000000000000000000000000000000..73aeea45edda653110184f5ccc55fc1e9849bf1a GIT binary patch literal 18159 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 literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should allow to sort by dnd #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should allow to sort by dnd #0.png new file mode 100644 index 0000000000000000000000000000000000000000..71a6a01713d30ef9293ae9a81f21330cf81ae2fe GIT binary patch literal 19594 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 literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should delete items #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should delete items #0.png new file mode 100644 index 0000000000000000000000000000000000000000..c43b42000efdfca2ab796e6cfde04f12d2767d80 GIT binary patch literal 23210 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 XuMcQmHhr)M^*S>XtnstM&R72gf}YKc literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should do ask for confirmation when delete an item of a used thesaurus #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should do ask for confirmation when delete an item of a used thesaurus #0.png new file mode 100644 index 0000000000000000000000000000000000000000..6de9e35f376f0d0ecd89fb566db3f0acf509369b GIT binary patch literal 12735 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 j8mEMrsU6PYllFEHx^y>cs-bMp< literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should edit an item #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should edit an item #0.png new file mode 100644 index 0000000000000000000000000000000000000000..0ce63480ac46588529a5502a1f852d7228e9548a GIT binary patch literal 18808 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 literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should import items from csv #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should import items from csv #0.png new file mode 100644 index 0000000000000000000000000000000000000000..b4e3881edf61d954c22c4a0588cbc879bbc2829c GIT binary patch literal 26493 zcmeFZ1yq&WyDzMwpn$Ro2`L2xk?sbA5LkfHoeL198vzlKE*DF>R7$$Ln?(vpcXxMx za}j%=`FKfR!=QE%Ai)X&xucgKAqCY^tcJ10-@mIpK*RG+Q zUb}W(|28W4M)oHg^x8E;VR7N-^7hx4tM6zJ-*R2sSUa8*GGrAuU=1HG|AF^J>Jf@Q zdQ&!owuZC_zRH_>&lRgFTKvGX|m|^b`naH!7u2T7Teh)TxMkY#i8LjHA zPOa>ZF8ex|h`%)sI29z9p#5OG2}nkk@bdD zDlZ~Ab1R$fa^um(p{$zvxqtcAizf!f%NntU)9ckU(}yMA@6MaBJhA3mSYE8Z(Z9ke>Q>F%(|6F6nz_Xy z?;IUicF0S9`GiyA6fsn=SL0jwEHMXVB2+lylipNu)zF)ZOHRw;&e2$x7ucYVbgwwQ z{*>+*a#PR@o55s-TSBs5jhfZe8cTWJU)igHfvS6%z*y0oCkL-M_ZuGc;=ltjuZBsy z`}bL4cm((V@^7#xCRx6VOJwDZmuLN8Ta01ZEXe4UDE+o5y)_mzc~d_t@}d{~QlPBf zEjor2Ea{3Aoi`an-O6Y1vQK6m+3bDAOaPDicuqHmhsN+xyp8!J-dM7B{yOJ^5i zLLoLgYF>2=O=sBNYV(VQ7rn_E0lL9MhZ#ob@thj2Zx#-@fspr!iTU{92<;VB(6aZ4 zY22@@Pm_}`j1v*cOa97VEBlDE#~H4o#<2r>GeXfONKR``Pw8qyOFU6FFK@MO#a341 zjYqAHq0HpQ&GHiz@LhaL4rZhgYsk+#7@K?G$5geR(EF+I#+v2kl`)}DMr`VsJIhl# zF=tX}LdOFnmZl}|1jIZfxL2n1{Xz`Mv9^xQc=sndX0!Or(wAoz8f(fbd3;**XFl4QlVI4K&DZk8y=^sLoMYSl@KHvRD7a6R+`kz_Lak#J)Gw~M_2pIYyZ zjxvKh#KNkJs7L-!389&+QG-8DCW67P75XdZQ}hJ)47(7AT-0>*UWd!XmQs)9@*U07 zH>caGTw|rqbTlq$-()o8McOlCF|ArE&87p^$X^+S`)|w+%P5)uVX23*s&Jb5ZB_{k zi6`bnx#&DTk4oI?kn|z$JvJrty+YH8Cdyfsm@tSrXjOToZ!EGgFSv$=Y$us^SO<1? zs2t|oZR+%lX31u17g$xwe@4eT8;; zEIQlWduz|qnmpIbEKiN%;p+5BsB*NnR>&kFiLTz&YWUkN5=`5f`ZdeeJDTX|X~)Y# zYE8v+Lv8#gZ^PD3?KycOxkL{RTne&uG;YR@wId`YctA(tW4LV1^*P$_C#7#&Cvckw zMCN>Q8*y-v`x3BA)yTBV5@wWZK5F=MenA|bdpdHP%f$i3Fot+Mkja*1?KW1Zrpik@ zC7;+cLku&H9hkUbxMPtghMJL&Gsx$ zMr@{y$M~z<##9@B1}a(o@xET+xbnbhMw&$0Eq&>T_DPOKGY>~Af4mn$+tJFebk#l< zF1=wfi4e5Sqb^#DS?OOveHm>T5$kgJmNu5h@osGCPY=C-y&zmu$@QN1T$`P2$Lhys z9L6I!Q=@Afa^u;S!`_+&T#ZFb0ed_Pc;qxO%X(t(KOe0QfZ>}(H&H;vqd&vxp=xe? z=cS1~S50U}tVHsd=+p3-B`)Kd8P=lJ)b4D;tmRY13WqI&)&1)0VTtY&{i7sP(=LoN z8Aqx8C662IDk|Ld_wp?f_j{ba8sl@O`L1tbXhd22oac1dM%9d^d28nm=FaG$)bNL+ zk!9mG=)`BrruP_yxy#vxcSvn+YE?|I>Sl8bY3x#^o9I-0mP|Tbk|5RKr-&-=UZ_6YzR_MNHbc!d*&79qAer{f5rZrrvhTNsQS#Hl{)@SHm{XzjNwzVx4&zTiBdrwnH)$JYb;$uK!XAB&zqC!9RAzpi2w=H!iNj0j@Q)yB;U6wXzwX|qI zxIZo5!)w15eO=Sx2YgC;d$Y!j_t!65^NAS_WqM}kkjRxb3(Z3fn$jSML6b#kI%cCG zQ)*&L@AGz_z7nGW{Hay6*>R>^Snjq*veQP5-CO(oNN^ff68-CZzM29rgX(H8ex&AU8n8KVu7 zm8xx(BV}Jk#FKKNx!QKh2;zB37-F5vfz4mLzuXh`b zjy6Y+6~Wvw0<>rSXv->odjCjCZO&G`%kS>KU-sDV7H&8*W7!85ET^Meo0^9`?~s2u z>gKCvsWR04q}?PdniBZHi#u)aW1@c0_7bu6tsgOzEg<2LPVCw8FsVvUR&nL0$@?!8 ztZB!AKD4HmC44v#JjGP^MOB(oz1-}+09EB0o-@^UZSWm`q`yw;uN-)H7fD=MmvlQR znA@XfI;^vHnVgpc+Dx@M=rM!ao;`)l)Jjkv2<0p*W_L)~q{E{YX{9l1Yq(4Yg}07a z-X-y#<0T@=nd?G@qeb7!X!8|}2fHBBHu^kM(cTb3#!!FX49!-V$|J|BntCOshKZbP z8hi}<782_fKe&WGixdWO=*d&mZi+=9;&Yb+dh)cvcpSil<~ThGmk)_D4iW!R(p#Ol z^9HrWfCx62EZv(y^u4UhRcD>cTNuA~+$`lisj(`#ewJ~_NJ{u(ynzpV>@+9(Knx!y zr?Er@8YYWIE{VrscY|3YTD(k^WciS4)&Y9AbQim8 zsrQ?kNio$zDzhKppF@&{2r%!B4)b6tE-q0f<(7IH$-O7+z=XcAC^1&I@1X$wgncj6 ze}em0#+UhL5ejEL((Qjx(8C>GVcU9E$9z*LdHX)#tCxP0_q}){Gk;md8H&c)ydY_P zaq8UZI$a|Vi8{7vlOg_R0exnMKbxRAd8UEgB?8h*yWHtRQ{WG{m!ZmWwk&5xVAgTC z?N1nHB$u%L!h!T|{ICA$%v3axK`9LpD*AbAuhwh%B8@%@HO%_ISuN7X4HWz%+CCW= zoMYkqWu_w!*B{zn_NYd(!!C!0z$M$mW({-wJrOq=fgB+cd)}(~Z^1^|ux`w_~APrj*O#OtP4*`7s5#t*86Q!jd%dd4_hmQ%C2g49^B?@`xBgQas1Ze)(_N z+kJw~vUO+F(v%PI>Bf}X{tUiL>{ZcxJCHDe=9(|r5HBoAzN!2kw#Ag9 zD?@goLL7LS=1-E@(J)-{qWuJN%i|(@g$)giR(CB`h{kj;j0evIuIEQ)PNt5P`F1ds zhvNolRIMJjG|EgJ?FrVgRep?d!-}Y{Fz}`cQt|i!@S+=FZRhk5xzSI+SzZ~v=)($t zBPDhqP5)>CoJcN}Nb>zyVGa}Kc7pxE0Df=g!Xtbsx#@g1N6kBpQK|vm`ZNB0gAS`O zgZ86IdV|)DuZ6byG?|QMaY>ZVjf&m$gj5kxwUSNZb!^AB1B&4IA6fkkI${U*Gkxjw zhxQy|(S+lreHqm&EE`JP1N(iN=sw0#hX_>zlI00E5?&5}2!Dql+3a42&E^Rj)(hl7 zhw>Y(%_pYRbO?8M^#)YteJR&rt-gOB|M!s4v+&yXSKB*cPo7%mRz6t|b6P&8U2ey2 zJmi^F7J5ngAwKYB;C41081CT&(t25X(&^gFNiEF-1TtdhDMYxz2+NGy)U^$wE5qF_1e# zL?ze4T0AHI=8JK)&l==me7=prBBxk^e3D@Ee3&-9WyQn@hj@5X!4cYosE#eq8q^cG z-0EvSc4R#Z<~oPNUQKUvBNT%Wc(~#e;Wh762LREYUFPJ5N<^$)$0*0f=qz%FMx1ZEfM>UDyG5TyS6A1;B$BejsU2H{8~nlleSAyhqWPfK#p?HffIw@` z7yjgfBiedsNLC@H=ywooK!F&D29gAO`r^gD!q76HGt4h9>QjIp)l;K8U!6)wr*Nq02G9+F4xp`?-fv6| zxVR!~IHot$$C_74X6+i8%yrxDhPNz4i69+aswwAk1-Pea0QvtqpCPe{0>OC$x^O76 zxjU6V%RdzXWIm11!_`O?Jnv~2j%Xy}HrTr#{*TCk>pHY>Ey)eh{_OH@s2NZ$PKF(x zhk12}U96W^fQh&K2Rjb&m0TVVLm<*p)+sL=0S>>=B)q)->%!7qe;vyg)z?b9eiKsD z;9*`$Qb1R7J*~LyE$;(m1y8PneDXGf$*f2`{30Txt_1eZ{J{cwLa3eP(ZK8g&H_$y zd3#e*?D$EgN#rs=B5xGpMjGknKky5-=6%T;Oc5;635IH6omN(@=C0 zZrBGkZ^|}cr1n(3%Xh1l)^Uw&&(O>npf?SLcWN_3erSIxd(B++`r`Mei$k02ar_&+ z>v8QR`TY!mUX-o;S((22Ei;iT*fr+M=j)e^-VjOud!$=T-#ym5YL4NjjTl;RyXmk+^ijrDBrSxA6j*n=!>uFJqt0HJoH5bj;ao-RzmvK!*+SKMZza!9ef zcSw;{%y2b4C8^>45U`g)1XYkYa~?<-^~uo3Xv;+1-3uT!uSw4cHExkzn-qV&7ZW7yFWF8w5s+vt_fW*qW8>tdW!nj$5soIM= z$3kaeo~rxZOC#HR2~RSuv+TWN17palmUOYQ z-{U6+b7iL^E&+svJ$+fHx(4UG*Qd$fxAdAs9fi{s9jO(XPh)Qp%0Q@#R!2XkG@tl= zOZqL<5@7rEMgqjvSwNhu#ENu7IigtXz`O*anE7_ja7?8!fvfFD(Vu|hv(xn#Xh^A} z8pZRq%F08-K?niQ-e`U>TSs!098^CGf=Z8{rA)|nw$sSSFGB6vwzuAlK}xTz{P{dg zowy4}KXSEb*}GRejp~L7?K~{aO;7Nb%q)WORa#J@k#f7~2MV+-qZqyxa$(e~hK7N$ zbU7|xx_`d@;6d#)h~?d2dw$nTMQ6kQ^Z$yXQ~fc0p+kU#zwj$io7_m9(?SbExSh*s z`9%~>%RizhcE6yAnTXCmIiCOVl9nNNqcN1%;joiaRc&&=WjX59+frI{lE!dl@ngm2 z+EUS3XYKkS#1h#WLdWe#{0F7A2p&U(8a)qXm6p<8_Rh(Z3Ryk^b=kHz7?S<)P_1R{ zi4D!!J_3s&DdPDB={M@S7a4+FI=u4R`%q1Zm=RMwnX-D3sGNE3W|V|!ddK6?sNGJH zKOM0x%KwaF=+M#lF7duj(Aug=-@xtEh`KcZf8{JQ9 znx&{(#kii`+DnHYk+8#3`Ysz5tuDpw!uoIVAs*zjA6})*k1-&J#@TboI@}5(NA93RP#)sd=@uttXL06ltt#ijWbU&Q47Gw$g%?VCv-ue!q z*!$mYQ{Zwkbb5&J>txQq++o~1+pNuWD@7xyXCK~hfRLy>)8H%J+VmxrSL6$G_(WX$ z>9>WZ=Ev-d+3{c+IzgO&{R1YYlrQrx@(Lyg@HX$lfx&Ur~t^R=XSX@z4?Lbm& zO>n4-N47QvZo^Bv@;dRPwyEz99Mff`ABdSw-f^7SZsFL{M7IaEeIC(yZ1$Z=gt~T& z5EaiJvMDW?z7%TU>-WkO$%hF`O}b@K3?-xl9}qqEt?MG#BlHy4l!wC5w!UD z*o{D|_L`DANLIa;M3qoL$12|RBK9Ep( znh+b!hr^$p7ED8Ks-G3AH9!8jL*W9XP%4qFTP^uZA|hqg>Ju5-rsb7F*ha&fzD!sS z6wNQzA8(gdqS326?dXy)5~GT?P9 zFLI*EJpJM3QeO0+_5z-XHce!)nXQtb((UZPx(7yT^O8CI45?XvaBFZfmc7F!#9y|h ztF?HoaVPUE!yl<8oQO4teR%eW$JX;XeUO1&^HUAup+7PdA=RtwUgwemtBox-4`))a z^;1dEmIlDWOCWlR-SM^1Wa}D_W@WVZLu>Ur)wk-MmflD1gDI{xLgSf>ON3B)uZVQ= zSo>;}rf%r{;Xxal4&%N$&@AQqxGjqB#@H7HcMEB@2Nc|t5gcxQ zEhE$`s-N%n69=9|Zld>HsO_iR3FiGkedUdJ1ar;>KD6CYn6`^X*FPS$M5n!5oLQ&z zy_nQt-l9`Tn(v^P`h0!8HcVBm?PW{M&#bC8KqOs9XrGwpepulvJ6T1&C-@fsC<1Z& z3TQ}NAdgSSQ~lA@l-b%AJntiU4@UHc-i~sAVj71UN`BidAmm`f7>JG|2;K5s+sRoz zA`a6~YUMMthC>Wg%w>sq6y!A0H11S7nPZ%y)3~T~R=?&e5OJ7wW%#DRd$2`b!yScc zb&pBsXVGj(`0?fjXN*(4KL|?jb*@2a@k}I|`Fz<6TSEW}7=d;e^C8|JZEZwUr8pWx zNULS?@kh%jMmM_5==PUF0}VDx6^(z47z6l{ziA1MHfVdI`6};L*>akCjqeN;9VQZe z3mzhRSHCnug+qk@v99s80>?cA()rmL0zU5JCW>>^0fpKY*kx^(WWwgGdF}NND4kYj zQ}-9S)+V{-5#0|GA?W7wj!&W#*TE$HE`tVP??)%HZ6_o(@S(kB>|B>?K{P zo4m6N3~#w(Ccb|F*seLLckXHIKgBv!8uI}H226g~Tg7JCDDv8V_d@vT4 zSI>!eLVR8ATV}$C|71%-n0Tkwz;k=)}gbtLNGEj=1O;mkj{EaFqE}^^hz)ZpAJG$adtx~fCA$U4JyB?pJ7Glv#O*ix_Q^V;{a;>G6P)v zVX#E^=)g-eEun1t&jJ>_%Rmm2U1VrrPruY0AO=8_zK}e!=q|a|k;Kzr14%17PzBO# zDRj>>S^y-N?Om)ahxZuWN%A@oio$|T(S;@kJCWu5>+?DQ{E<&bJvOsI5WG{dPKnhh z-Z{LBcR$i;p~T3EaA}liyo7FSDh3S;Ssn12i{3fV?u_?YOy-}4YR~(dnFI=Q_*uD3 zBMw6bC8a@5D??}(IqxW@`ie%g$%8qx3=EJX2{9!sa9=sHOyIt-Ce8b4>2_qtZ97K? z{F~6cG2>MReoH|gN_97rQTrU;svmYft498JJft9JWE5c0!m%VymE#v;*Sl-gJIS@s z9}X>wQSNP+V^TiXBS5q1FLB~W*`Le#_Ij+wSzo-XRtjY0fN7K1E3V)V@6gH$;ZoYo8V|eV;BF(p@uhc}xR1K! z;Hz@_$8EX2AcT{#d{T7vxWS*qdK@!EV3$4@E9&{lak1YmxDNU)luP`8c^@4WWGy(5 z68e?E{0E*JX34!=H+2W}LJN~^*1A2@){<}0O5)vZ>!c@)EC^rB_=C>9g0B4}Qy)oV zr|oT4;6P1e#};l5XVy%UCgrkPy^v=K{S}76m|P8%_yOQ% zV^tmrs&R_`O0%`x&VKAC?~p*6)bc(3vGgdp*5dSn*w9-O?h$g|;7rBK&9uvi1H&{59K=^-4-_vR zE|6G#4uznSor%yfF^-Az$IN_xiC(n7srod9q%5}}mN-+jF+oXsLP=&-doJLnTvBP# zWQ0+gN>5{VU;#GKD>g&ZM_T)v^1vZG51hE_Bao_$goK#Rf|b)!XyleXCiEh)D=xDOz`nh$PN*fE&Tu9LL?VUxaapPL0=6x1J?$dceFkP@_6D~o}{xatVvpRM62!BqcAvaEGD1l z4L|D8mTDHUKDIxOOI#MnLY>@lI53DGW-<)7=QPGVw*G_dbl}nARX=%bJpj)ua6ee| z*V5Hu`wKSm;h_HB(^S${{wjZ+XY<@lfOS0Al(F;a(^{C!4ed1taY%$#R1CTp6j_vw z@#D_M%N^76#QA{|uBaV^=Kfk&uZ8kbwsAr2T`r+#7$SZ7dg!^yzun zs^YNnzi!QWm?CRM4rhs41&cnRU_Bj=$ZUN`60mhhrxBKTt2^(jUM`Vxx)Ufe_}ugE zI!6BrJ|@ZsM_K-}$Y}Ks_442b!&uu8Gm=SNISfGHSfN>)1PTYoP}?KV`<9Cf`ogGP zP7^J1;SQf79XuVmPh%xi&Q>o(<2Z)%$^|cBJ>j1$T?_2MxQH|@$Mw}QU+HR@Un195 z3K`iG@bTr3az3P0cQ`Nvo+3q#oR-*a-aAd&^s5I+hC3Z+O zns6)IwWPV5X#;~x7$m1yn743YRjqKoqRKITAF$!QMMt!p#f#M;nC%9Q*8-duI{cmkLV3dif9R(J?k0 zBrl%cr!Z#rLPMRWyD>|P;=PT6iyw_~?h=2la zpz5yPpyL^!CvUo#WafbUyXlV6w;7pK3u<0?5{-U<+JVBp?c8-r&COAjZn5MskEA9j zaAjqL21S(&aWXqZrCqclX46^l88)`K@G&U$v)AoF6$E0S6Mqh*+a_OOR-jXz=TE)1 zf5r1rDK^i%8(Gur^L?iXf|0c)fGR`6XQ9pPG)Xb|-&5J@QxY{nVM~BObt95R7NoOK zz%VQAzq`w`N{$Axp&YOF@j0X>hYIsc)~1de+t>&>U}KnOP`_TYEr5_*alxAc=kR&)l?sCx2hZz z2So==CmC+Ye352Fm!&-3^!iS|T(r~CQsh7uV@=}SH$bY#1%&{OWNVa3znZ9$jP$Yi zYVUZ|${BZMvmr|C(B_w9m>xwN6I@Wz-!hEloL;|4o}cJ@ajcfo9qWF6=8 zckm^~hj|iPJ)tYKnwWd9QdTDupi?DP*fx1e&bbxr zqUBkCV`~-&*med})Ka7mqu^YiinlQR5i1V|Zh$w!&P z#wC2v7gor%;|~uh@f^`FDN%Uqe}-h=ydXy~L@V?r;9!n80H5_ix|{O`C3s6dydCM3 zzI3KUVs0e%DCz>#eDR@XgNs+Or+*7Qc*1m5flhH2_%|Ie%zqHyO++1J&FiK^xO`=u z!o=Iz)2ZZWse_=b?jr^{g;z!@HBY1wWpq0;EWXQVnG^nR*uZ^<;qBeK%&_g9yRVh! z4a|tnO)wCGrGNq_{$TODV8pX!rfSBT`2pne(ROheLs5L|UjQ9Mgp4cENS93t@R95wX+uy-AKyiL zco=8zE5IFXE9#42{>Rbr_y_G?(h@&0>7?isbRp*xY*dOjz?lz+gkbAcMz)FS zJJ80p{oU$Uy>}Zx^j)<7+5&@1+m`|!G64M9D~=>>px{n^$%VtCt{NWtpK!4M73u!> zY2h-WHlAW^?}@yMm!*Q!0>>V#Wk5c+=SVyd8{e`KT`3bF=-mFX885FuWcD+gdqm*G z9(@F_D7qhl3vAOg5KckKc**iUK$j|eOr|c47FU1SV^AS%?*^|%xO_+`tEO@~s$wcB zi7h(Du(j<@P@928X_OzT8>$Rg)FyTK0ju$<$Enl3BkG2@ zjr8cK{;|+Up{}bjf>%o!()oCQS%>_a@GU+)y)6>+f*J?d2(4xs`H?d6ICVDav@lYKZk1m5_v zcehSb(XX$vW-N(D7lNHIl$DLh^&%&K(lELrS+dFOY7uH+>4bd9kp=hiGN2^i3tc|7 zsi}ntCZKocQRL8doFzt+kCg`}X>mto<0Iu`RW78vlXPrF)JU>+ae62>rbqIn`TGND zUV`2lPy)#Th(_OnKY&v^{pDFJt&$viqtJJ^-Bj3P33!lE`TJ75w$uZmF-}GP7#$}V zLZxQJfP;8m)SR%k8xNHD-=c}BZ{|{D8wHj6BjqRZ!AC^2p#4HfSLAO>U=wv-aC_sZY+2$z;aO`-y15o%6>$gK~o+FG@P4?XSp*25F49WJGON4!Pm(Ct}o|on<<0 zig(3}uO*^=Q+tNZd)Td-i?1c+woq1?9P@u?Xh?+@%Fplp+ZygEjUeK;|7R{+JBVf?! zn&wmK{#{q$82B(%@K04MjaFS{-|bVUnE`9-I{UY?Y=3$@i&j&7`v)dGz&qk_E zSp%49if>KCnU{h&qHLaPd92O;kg4QvkNzDx7|c>}@bpD*XQNWi(rv60G+v&KKks+8hjvItQ_ zrMM%_DSF$VS2^u!*T=o5aDSeMP!4njs}PGR+F*@x!!!1Q_jy$JjT$AtTK)2S=*jbYX{Xxc&x)35urBU zSI9LI?(Jn9^!WzdPe#yG2TSYvj3!?k7M!A!?dfyuz)kD%%?t| zBfGZp3me0ZHVmOa`K}PK{b44>L>6;Ezv@KLQOO?}8x*^yd-~bk>7;FMncey?wMcP- zo1Y)C9t$!FQU8Sro7i)}MsSqBBO&2V{6@5wu06A9*$~?uxiQx>-pXY^9&=x~M*Q93 zti?pa$rF0k^BDs+oR#m!t- z%So{~y`aK%v6=Nl#NZ)n3SCP-395kl8`sF*SHv3k1B7X}r!VWWf?6;+VPkWTTh_L6 z1S>lvL&oOx-_tTM#+M6-zSLvOR;D4w^|k5x-46i~l@oMf7zqnohKm#IJ8N>*fy45{ z6N5i;D6nUk{4ljaAxR{D;DRr$L*UhFRy3KCE{)hLTBMl zL~ntt{IrfFbH6ayEA1I`-61$Y#Z~+E+x2rtjAY{ zV8%32(rEa;?x3B~`57VIpf(Cj%|l2RfK2A=E9%=~gS3p>UqcNIC0Pc9K?go#7WM#* z?ELrU<@^@U*W8jTr=cRsjg!DI2=X=1gLBiB@1jJu)fwczQFzlFV$gL99Cdf&sO<7F zwr`RZ`mVTM&bD6D^jEce2$d^r6_k60fv60xTJ=@?RXf6>t^{cco|d$9vnxNs60V}5 z;&IhE)c>W^{(a7ne?NRK+IS(O>lqbE0vqfhXfla}enrgMDg!o)eyQOD#sYS@6y9x* zCiI&xLBRFi|9kTi*_1@?1`xI}VDp<29AxmHTR2%r3^M6`(_8p6=oKKqaBqV z$E@sV3A}UjB^0!3L5MH~BImvhcl5oKr_JWv%f_$hd&S?!`zN%#Ul>bz0ASRno@qJc1HPt7gt05Z0~1`dmY5bFo0oJK5?1Y*U| z$!n*!d#^V~sNosMG3M{jR~uq4kcBX4Ax6VH?GgdI%#ViNIxf|X(!sxklT@vHydyPg+3lyz{e4h{a2 zFjNjwVZcJ(Ve)9oZ_F7HM%m1LR3jKKyZ%SdrtDzwfo?YYOV1gr!(t8qESk7&m-zz( zl6?-gR8`8JNLYCL%XKXKCsj?KmWIk{5yvUT^0)I>Vu2%aD?Yz~OH$waE+a&vVq5y$ zqvs;G8sD1HPf+Kapw9s{#>BGf%2oZHalh6>f>^ea2?*`>--@mZMw$GZ7)6MLstbH_ z$DmNRTA)Z2WcS-dM=d5>A7ecWxWS`nzz+dk1vnCF;Uz-#tYxOAmBdT68F3pys2c?2ZJc* zrHf)(ie9snQuf*?B_a2BEb`WqFFQLa8CCB%){92I;Spprgn2a?6x(iUqx(X76_kzj zBz+1V7f+;!V2-lHZp`&wO#f;!?uCBhN>UF~Wu!PHL((6{DJk3ww&vqN7 zo{h8LkJ?RykEmRcI;xrkMa*o-Qposj7^LW@(8c-;FJlp4Uuk%*HI+z6)cL@=p*^XFgU8e8Kmq@vOn z*7}%q7WP)ud5r$z8sYrn7kPRpfS|gd(Ngqk6+z`#yb*yy(K2>=U9}vAqQ5ep#H9jI zo}WtmYdeIZOM@fLduwJ+C*!P98}+UQY{Qf7!&cIUR<@-BpYsvl|33j)kSy`p2MaC| z2UL|rqeqnZk{9w_W!YwqEMufOK7eWdcWD{vwU_7L$lFUI!Vdmm`0+WhQ`(WY7ij;( z=B9s2W{_0@5S&qLjV(w1K-9tA%?~XpW8AkxwRnE>6c2yEeiEi?8n!QBH!Osi8hp01 zs^%Z%{9Fs&2NLr!Er8EdMFNz{cDE;2uViobyQLPHY|17Il&~$aVK!MkfQrC_a_s0o-k}sDB_`(Q zX(0Gg!`W*oqXeiC&xARf*%^T0j~Urk_0GLFO05dPw*I~;+2MA})bJhWWDTOJ z$V>_-sk77L(t!2fF5ls}GUm-lfR~(s^pfyLuPLZ>Dy?~&;%Ss z$pwUWgvJ(HBI>RaUXY@k`|;gI7qHB$!0s!AT0B7(62*QC*z5yea_@)@?z6Cw7Xg-o z)kwkB+{4>9OPN0HJ%BqprA3IW2>cv%MmnNf?wmPb&4Zt^ zm`|*%s_9g(qg{LgPc?>2AKQYD2&CAIMxF7p&|g1YS*7v0Sd?%thmv~JSJJ~=T?JA( z(-I%&J+kVm>(2Yyt2AkuN;bC?Q(YTZ661@!kk{vI$%muKDMNEIYIKR!f{avuEzW#T z33c&i>K(CV6+LBgKE^J|7U^Q@;!_`$f&CrnR0!8sIwUB z9XHQes}Tk_+ACE}Yc#?`lv@3xfQo~|y-|l1i;T)c-Hg(Md+p)o9B8YRTtYK-dW{x+ z&fwiaExG+h^A8I+B@Zk6ZvrN4gCzPuS5OW`YG3=d8(4g@y9IhrMWE)5}s{@==VORqtM;$e?`XHkKBNzdIYGJiux z@Z?LW;jIFW=48HG+4)#OR%A0?k|1A&ECIPloD$Cj?x{Hn@f{C`q-?<*evNROGA( zle%5mI;ukesXG7UM4R)H0!{_m`N?__v(&|kOk+B5bD(ybBK#qVQBu+Gmzpur4E0c4 z(1Ac|AOelN^YC@}MH?ZTfylRXXARb#|vmO%~Q(UUcNZ}KVi;E?tUa#!BM#t7D=H~9v^uykc*$B*hZ{wqI}X-G zD_45GqLOfJl#-EE{whpxIU|L2`^rR@DDf(S}VN(yG{tdNd5ca`k4=(p_kyWqt}^W7bY4Dg1k zTB7o-p}U-7Pot@Eym10)yl8O1d-MwIJanoJBEZ^lvO)~t;mp2D=oY4U17!0J;q#C; zAjAjaC|D0|Q3_^6O*HzCft>;=m~9pCoTs^!@+4Ory}Fp_^G2JfETAE{E2wTeqRs-;36+i zc-?*zEJ)_;2KCtU$^YW|Q{D%eRvVHolrV`dTta*|zQB^tQ?@+cFqiVrIJSN$@LRhu zWGg>#wSFLY7JolgN2i~WtmG#U^6Bv#)inJ^Ol#TG2|aFI2dx*O2!uMIRhm;t_^{hX ziYw)b40+d2+PC;-51D{QsLlFK3U&q0Sva&_r16FFjck1O5Ts<%U#a&m`^xYm@}cl( zTifnbJ2L6e;F#uxoUT;rKvFwft4;D$2m8UNliutocIM|vc}P<&FIt&Cps)2Js?EwVY%@m;Nq;w#0L#KcZGVuxzLpwM8-+cYyMpE~4 zZXEEcMPuqn-FD);H%HAT1|#hrDv@%ReDhq>5)nOfJy(sPL*4~;;I)vnw2Ti!0Wz(D zJ;4VizyO|%(NBfTUdEwa!qGic?|W_OCf&+CiREO z^GZodenH+&cQ8IdR>5NkZ?u7)>;gLj12$5x7nktGP#(gej+V}40fWpm@))IWl;2C8 zYr%}qyd^%NZtA>p3_1ix4~U5;w2a!0uT|Bn21A$^tRwur8~BR$XDYD+L>7{3qTjST zpQ2)>hKqiT7}1&7@LcK-GpF)tTzOHFQ8h);g|Dv%o&j`o%&*HoW9;JtHVpT49a&DJ zM)@~hks@|jHHv)ZMseSV=-a2Vyz6+0G?8wuzZuok4{;kZ`K#~q8HMWfQ zAS8YX(jA}6R^31~c?S|HE(49Aj*h;=IUAWd^lM=b>e!j2MTSxN?jIL z-ZVJIt&<{e08ZC+%&z_Fgzr3b?gtqYpC6JV0LnVR81+B+u!Cdi?rJQb$AD-6i6~7G zWq}u%dBD74NtS<*+Vh*~yhCI|cOmUEQ2KIy#Dp?& zam~NVsBT)yQe*tHtvlJCnV)t}4qckV+4?+nL^!K~yUo4zneKqalYC2T)p1?x0)iQ1 z;WKHpBVB@|Sf*#3txz^dQ5m`CNlDaHqjURUBDY$zYn&waO4?}DhvU%c(sYuYA@otF zbk~wW!r~`+i7-kTZg^ZHX|on1Kkh)z;^NC;+db>qHmzq5`dwmS>ei!1c;K}y3T&}X zut#oa%%JeVnqZ|9uaak9uDv)5t1;?zb#HH1)VRB$BHL^hYsv>6BtCv<*y(iyWNGwx4~+`vPC)^90SII1jQcG%eu8g>MvunzG#zbuG4MY&uE-g_RwCE!|4oYY3bvDzXWfcp z%@GP*8^@KaaVcFbwPzm!hyn%L_%cB~H-Wmh4q;w_=gx=2wH31w~japYWsEf%tMkd)5O*}!uU*khfSWWS2| zFiZxQ%vkII`hB8|8sh({>B_^QT;Koq90#XtN6MBsloSRHVzN~e$}*jfvP5LY(q`-Qi#UW5|a0chMJ+7&Gn!BatYa@E-CN#WB+xX>p; z&Nb;2sruF})ZUk}7W2WOsV)*Jz1L>Lv;Ufa$)F<2k`;C?(k|PjMHKF|xtMAm)%vT1 zkMu>=6!Ny?^XYB-&Vsk)8>xi?Yr|6F+)&2;J8a;@=EY9bE`RpvtZ^SWFqJj<*k|>; zM$KzYO%ny=60KV%IDj7|s(JM8LITTBKTsJMtC?tL9|19fC%ww@@r6Y2N@S8Slh@6P z=oFI+-V_%;Vt8MVTyHr7>U{l+wx<7#Pdj=}JeT*m+oUedwb6b}1jij*+$<;nYzc)0 z4eZG%$wKMbT#QLh%Qv#*h}8ALj>wJ6xw>-XfkGASmo6fD+^qv=kf$^n!l1xBk9TPN z_j&A|PIl~Gz!}YYXvU~#lVz~0uJBGct=R_bVnQW7Z;BvYX%88ckqi~2w1CYjF1ebN z(VZvJ3(gJW2w1V+xh*}CGPTf05JI(yE*S zZkr^)UL`Ivirjh+$Gc(oZX}a(oC%GbL)xruMK`CgK%A(4&5!VJr&qi+K}V<9opB>b zsoS-BgB*+ZTUt$UT`WK3gr<>T$mMf8UIX(OZtG}I8cWCF0iJ^Hc)+1Yla3IYNPCZK z_tMSJWRdB2yy4r2IGIq4g=3rj^N9#-&4ayA=C6s1$ExvlaUsy=j99YY z_fHKS`Zqsa1Q)Jo67pSqkiR~0qirqKE`M!Vs-kwILv6ujAfp({lA&)DEz>lS?8+1j z8!gm-D+VqKL4jABi+C7F6%U0hacXDIWcfycWsR+0D{(d&h~3=j6v%Y!45$|j`N@CSdw1;yqnNqf)Z zE|an6-oAPk$1oB0162x}`O4QB5;!vP-UmdYnu1 z60yE|<{7}+)wmc95X*YRFOM+U^J`>z?>k)4Ufd#G%khOeW45e~clz>o*rg8_G`yKq zW#Zn~S)wjY+`BIPOtd^SW~1-SBapXn@M-9~VjkzQteD%uWG1od*U5Eqr`i95g65!4 zOP=ijNabfBr zb}%FE73prt$p_ISnqKMP*CVClRzc&T<^Z`MMt#pfDe#`!ZEc0#C9$mw>_{ru%>Y2k$we*+T9U)%R;Z_FQvis@8 zojc#NF_jqOyAzHqng%LlW@9vTr5om!38i0b6W-RQ<>oK!Ps(u}YE~XwmL&5#mc5W9 zV~L-eQI_e(UdrI~h714`yhK25mF_u5$HbgMa0f_SgflMqm<$*XYD%gqYjlHJn<+gz zel*<*bioA#=o=NJh#VvH6*Eu6X6K{9vyFm#txY7(V;6;>KM0Vb&4S{aSYA4ML~Hok zp?e)*AA=UC)$bf^(dq!A)ABLI8V3Xp2Rs%;#Ob(Y< zRmI_!?pQcI%B9mUTujz++vChz19*jU)5?+8cBUAJhfG> za?z3Dp|MM#Ho^)Uvz)9%%cSVlY*YZ+Vkki#9|86e^n$=3t3RpX9rckL+6|uimR|@KMttQK z&^8+fqaQ>Vb596tueNNOXCuF5>6LevzH$oMf?SxG7=KDHKL7=fW1#wt6r{*?O6)h7 z?_L7{x@{n}i`b$0bf~U{*iR782m5zDMrW2zxbAqgv2;uwYd9_K<4t?Ni^^51h~el; zg9$7(y~eG}D;b=F`Ls}-(Rf&D+=t$vDM$J~Pny5nwQN6U4jBuzHJ44VSU9?p#X06c zpcMtAT#`yjnUHSGh^BzHRpA9x#4wZIwZ$+nCYnRCqX!V6!q`dP-1hw#@Tw z!6pY-h+)t=+gg6*YK*xXP?5mL*^g-!6#6A>o!75bF3?}o4t+#umWlv z5)1)r`2QIh(Y?%Gf3bSDhQc*Jsj24P@%bF49F#hyRG+BiFwQ8p87m}1K}ud8=JE9t7fj?YRkU5BT@W94kZyA z^1*R+nXEw4-xIP*v+r0H`hjy-Fy+W!AV)zv}tyNgOobO-z7(_Oo9yjn*s*Co0$9&Ow(8Cj}jt_N`ie0*7D8pKSl{W4u^|a~e zwx#c0=R@Ia+Nw#Fr#Qm`Z-|x25{P%?$fQE%%!ZbN;C1FnNukNVJq>%o>X#1hI1xk& zly*otG;hclQ1>8&<|HK?ZXYFSAnD2B>(5h9mEshDo*+ZXj)1<>6>(ZirH%zM6Rw7J z+vfnyaNtWUk8-62bl>0^j!<<;A*z~&>Kp)(epEm?TXk`s(dTwSqC5pi7bQ#k5u9D0 zP-P}SDZ@|KqX)zLr?TAuRPSU?Diq3aGW>w1K~;lPoB~vKD#Tp`9@LXx zkorTG@APUi;PUVrD3~*sjrLR@3T(P}5$yWGMsqF2nJhp-bA}d$7wlRP@Zgu;fsaMX z?A3_@1eY`nwZSQZ02$gWm?_3NMNfLnZrb4ULF99Z4}X9fr_KJfEB&=eOwD$re-iWI zj-rZ51b-@b2~Wf=2UfNz88YVOywkMf!QR6|nweCS2k_|SZb3^E6eZ?d$fRzP%6X zS&T1o1?aSXdqy@p)GPQy-p_Y`%-kPm|7WR%+c=WW0+;0QOoR8q+RqpKzE$Z>Hs9y! zF2j&^9){b(;4}e9Wd7%MjT=i}L>O$pE&I**MY~J!$8>x3Hj+=xuc_KUt|~4*ZoGqw av$!wD{@or5{#NIImrM~CiqGHt=l=j4tf3SD literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should keep sorting #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should keep sorting #0.png new file mode 100644 index 0000000000000000000000000000000000000000..76dd22a9a7529c0b3d4fa1c74253ee600bfa46b7 GIT binary patch literal 15519 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 literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should sort the items alphabetically #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should sort the items alphabetically #0.png new file mode 100644 index 0000000000000000000000000000000000000000..0558095a2f0fa0db85430879903d484d25daa51f GIT binary patch literal 29018 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�&(ggO0F#Tmw@YlL7-P zjSbk*=*~P36S%y5dbPE}YG^nab*l>`Kh`&&-_sWpw%O7e$FMlu z$Hw8W)!$Ie7cyP`(aQdp6NhO%F$Ap=Iez{O=h?S&pde$FMfq*$1L6N~2<=i;%{^1X zoc@;Ao#$&srQ^-6Bf&P~XjnR~W%1Rz)=Tg9*F89@lR`=ume-JlR2X+xl7uY`mk*e< znCK-*HWz_H3|gTs#bdd!3YenD;XGU+z84dZ6VjV}`MrdrSufqrj>8IUa`AmdZR44C z+Bg_xlB0Wtue0A-0-4woP(Jc(Z+YBYWHf&*BRP5>?R1?&E z!9$b*Q7o%I=Rdi*SY3zjU(geTKsiyJxMJ>tBEEtVFu;bV6=t@CraCc0NQTi$3LD0^ zq8VII-Hbe|!>E+@O99$K?^oyq z!;#~J@bZwRsP@CU={fGxFp4p26MNan6cdKkFN+>Bb%nki7mp(bwB+UGV|z=8#k@Vw z#gT69Hd&0X?C3JQe)jaa|58v`nga)00I}umvCfm+tFDMiOPe3$__(Z3BuPX!TQ$>bnP>(!rB+GeW$l0c}e5up5 zgqP#0Orv^5I=PQ@4UoQt60pWfn%qW>&A4TJ@{tAv($c!5cjtgHnS1EK$sJF}uSnzZ zE<_qBqK3)ZAb#nUNPM+KeLeK zAfP2ds%w3vs4D5J7TUy$3fL;DG#3@tMf6^pPi0Imyi>U>ALf`s*Ag9!XKE7t4uK@7 zr#E+Z)W8`p)qdHAh{>nCwcK@Wg<7dmyK@~E1M0l$ps~Id7+HDLD+j0ZAx~(G4=W0! zAuO^!GmYw_{fXBCKwFk^-QfyRtIi1koY!sOJmoZl#*cwtKyMiYz+7aSIpzFEQMOVT zV_(W-Uw7`x?;Jzn?R?=n0!bDyz1LK8W4Gd|)t;A|`u!O#iFC`R_28!xpx>TiqD|9w zdUS9tmi2n4#t88S80@LFy42gpb~UFwVOy5rE+1n(DEE>q;h57MvVm|Bst(=cPd6E@Tfi&=LFPa zaib@^6iFVVs_qiszOXvE|3Dl)nXuKtxznZ&%|F87@KUlfa_^G=*W;iL7(q(^cb2nw z7XdjDI_y=_r|6xqWFSMliqJqt)K9e?UZrZ+XE{xykrU#?{k<@r*ST9Yy+Bd+AQA0)N_7-J9#6j;Ig9Vmo>;)`2SLpG@{sZC|cIH)MISP?H721w@T zf@RX(94Jc~6g$;dpU&A3U?=bZGM3SzckUAta9%PJq!7QRb2A7nGKc~x8$L8Qw@fN= z3fqg|&a?PsT}MT=B|YgC?{N_htkv`Y34>}vsv`C2{=T}ga;WyZ=$CT8SJ5T!P-4Lk z7P>eM=jCi~O)U+`k~KAImrpK}yoPG8&O*EEuQpNy_SV?9nEqdNB2t)3u}qz>#wS-H_O zIUY=H5v(*$ww>cX{i8p+H2^}uJvTwBZDA*DNn)>u-mZ&+==i!F8=RY9hD@XdNVqeY zHCpO>o&#o<8Ykqn>TFkM2E$xc^(qh}z_bDS{9knu2iIl%vWN>mf?OH$tX7J=XS@7I zwa>2P2)K@as1bg2Nen1@M&}sAvN|gJvv6B~Ibpjk^U7Pt#fFozmrYG$R73#X=qZef zh>JC1^`CN3uotsfeYl!nem)}YNCmuw7fdDX4Ov1_ zcXyTTy(rq3w>JIF+`C5psoBQ#j}{$;lO`#n7ylyFif|!hl+M z^olFp#RUNe0G^wHx7k$yQnCh%vXlRE!PuL`1fN!@3E79{$3Sj?&si9&y?&@{>*QY?{?Pum zJ}53^^5KyfWU!pwOp8WRrl)niyj)L*(z>zqy?rs!zz0Rwc?U; zD+*AB{nM=-y*0oPn#*EBy}fPPvCpXM)wZkc>wWhE@594Kfkdc{em>^2pyEP5*`s6M6OA8hVgM8UJsLlj>9-iUMJRIPpb1Yeyd zCKvk;_()Mzc@2Sjp?BvOW<~=s+Sh&KWYA!bYP zOMLFBGjZmr^X-*WPC$G-S)6hekUe_I0SQleemvmB?aJB4M`h@T-^?6UOj;t!NYf4*)ki~gk-SOl>hhdAddYAL-l)qss^p?|TKn6TxIymcCx z_WV6KCQOG00%(|}W#g!Xl=gI*PJ>U4yaVZPuiErGuh@N>a|` zx4kis@biAJ)PWAcqh>(PASE&M7PXZHPzvR}PnR`H)y6mRan_>DU;e(!?212J2>w4@ z2nC!A5xrFVV)bsKgGaXLad^3EsX9651GZrX!`i=hM)Qa4!2dVd5yrr6So;_5NUdhq zX~$X@QjErll$*YNn@I*3Fr!kmSFaFaV-d%rqlfQMfBr2&0Jre%l|OCR#)Mlm#8Fld zAK%L9zK`7VXJ|iivN>Kzg2N`jDSxbix&CMX8%Zv}2Y&QRM@t02Ihh2*)oXuVh8U|3 zpp;^WRaT1Lae^j;n`{FL^=a`e)|UB)QEz?yJ8;+ptrQivfXp&&>D2YvGqgxxp!KfZ zLq$abvgX~xKZ`iUa2z-Ydu8><54Z#n1#Bu&V;I;OFvA6P)P9Keh!9v&vh{#uOboE)O0`shCl z5cM!h%C|iD6&w2$&l@6O0H81;y%#sCq&yat6cyf?=0CFj;>3!P@#O{qL0(=FEq|{M zH!TYAH&_GM1&Q^)NGu`Jj<2=f$OB$tSq2J(xl|y=2*IzUS4ILC{C1)QLFt%vwAo&$ z@uuUQS3}pS@uCCYV_QDrCHD&aA0@cGs)e5&=JkQx56pA9`&(1wVFJ{sqAL!+FL z34+Wj&bDy9QZx|y;F^s4m-Qg{0U5?I%IR2tJ|K`ffGvSa=-U-f#HatzAG&u^N2;j( zCIE!qmWJ)euf^{RLmi?aD#m=Aj%ZsCY04-6n}C8DsTAtS10G#4M``Dll_fbi6Lfc_tz>>G!v|CQS7O=QyHGfTm-`j@kHqpPSMx@gZF~Y; zV!3Wd$gO-hy|8?=s~O7A_%tdbW=HO`bqPhIW_{{LG9QQ-!!cyIWZK8jFc~yb9{y%f zm7m%YB*Qj2Ia>3Bc__5EbK}e*YdVPmQYOa?Kql5*t_){0ih-=CEQzMO%Q)G$1q$|k zZYe4s|M(evRg$CfC0{aIS{WF14n)JS`VI#!a+M?6pR+>R0izfl1+r2|J3P6OVFf9w zYr}g8Ma_>jV-^70Bu~jd;sZ~JW(12}-ub4XF=i~Fj2~zKLI?f3&3b`gDNre5%utWIt4rm7EHaw6Ffb4Zm$?F0844xiKJp>Cn|WvQL8KQnNVSgM{|Rje|dW z+L-cF6(>B0ckT53H}RHGtf2fRU}T+)n?Gg%G)$KGwv-g5jT{-zXDpmPOT{w1u43(~ z=jJYTQ%CS074gErmCX1Vc11y3mm-C|?%Pza)?Dwca&T7oa*IJZrA5nrJp%h!X1@IP zdB-B_d1iL}WU=l*nDenwSE)m^HV2NuL9FJpS-iPnl)=mzNoOyuuOHSU1f`W78FA$V zh+(`m+K%iG&ULI3?U3Q7sq5LN&FHULl(e638-c;~kY<~pA<_%gtR2ycwJfBXO_41( z|ANB4`QGThX4aN4MF@6ocl9B?tJee@QzG9T`LDz<`!;<&Jcl6MjLCN+rZ$Iym+(Do zZo7+_7M_u~w^|*M&~*ntHB?j|?S*OYn-p)G0S&7)a6J}Ocs`CwGa7I|tXXW7Tw9AYUIqMhHFzNaav;_3THv?5v7!j@Xu$jpFT1D0IJhNv&Zl%xd02dW z-y<2Z9HxJcttZv%Go4Juq0kSY(Zk;^69sW2}LAo?~;lh=AY4^ROR&!IU@F

#FE;BZ#r;6 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 literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should update the values in the entities #0.png b/cypress/e2e/settings/__image_snapshots__/Thesauri configuration should update the values in the entities #0.png new file mode 100644 index 0000000000000000000000000000000000000000..b4e3881edf61d954c22c4a0588cbc879bbc2829c GIT binary patch literal 26493 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 av$!wD{@or5{#NIImrM~CiqGHt=l=j4tf3SD literal 0 HcmV?d00001 diff --git a/cypress/e2e/settings/thesauri.cy.ts b/cypress/e2e/settings/thesauri.cy.ts index f6ee68d134..c03b505178 100644 --- a/cypress/e2e/settings/thesauri.cy.ts +++ b/cypress/e2e/settings/thesauri.cy.ts @@ -1,11 +1,16 @@ /* eslint-disable max-statements */ import 'cypress-axe'; import { clearCookiesAndLogin } from '../helpers/login'; +import { changeLanguage } from '../helpers'; +import { clickOnCreateEntity } from '../helpers/entities'; describe('Thesauri configuration', () => { before(() => { - cy.blankState(); - clearCookiesAndLogin('admin', 'change this password now'); + const env = { DATABASE_NAME: 'uwazi_e2e', INDEX_NAME: 'uwazi_e2e' }; + cy.exec('yarn e2e-fixtures', { env }); + cy.viewport('macbook-15'); + clearCookiesAndLogin(); + cy.visit('http://localhost:3000'); cy.get('.only-desktop a[aria-label="Settings"]').click(); cy.injectAxe(); cy.contains('span', 'Thesauri').click(); @@ -20,104 +25,188 @@ describe('Thesauri configuration', () => { cy.intercept('GET', '/api/dictionaries').as('fetchThesauri'); }); - it('should add thesauri', () => { - cy.contains('a', 'Add thesaurus').click(); - cy.get('#thesauri-name').type('New Thesauri', { delay: 0 }); - cy.contains('button', 'Add item').click(); - cy.get('input#item-name').type('First Item', { delay: 0 }); - cy.getByTestId('thesaurus-form-submit').click(); + const saveThesaurus = () => { + cy.contains('button', 'Save').click(); + cy.contains('Thesauri updated.'); + cy.contains('Dismiss').click(); + cy.get('#thesauri-name').click(); + cy.get('tbody').toMatchImageSnapshot({ + disableTimersAndAnimations: true, + threshold: 0.08, + capture: 'viewport', + }); + }; + it('should create a thesaurus', () => { + cy.contains('a', 'Add thesaurus').click(); + cy.get('#thesauri-name').type('New Thesaurus', { delay: 0 }); cy.contains('button', 'Add item').click(); - cy.get('input#item-name').type('Second Item', { delay: 0 }); + cy.get('input[name="newValues.0.label"]').type('First Item', { delay: 0 }); + cy.get('input[name="newValues.1.label"]').type('Second Item', { delay: 0 }); cy.getByTestId('thesaurus-form-submit').click(); - cy.get('tbody tr').should('have.length', 2); - cy.get('tbody tr:nth-of-type(1) td:nth-of-type(2)').should('have.text', 'First Item'); - cy.get('tbody tr:nth-of-type(2) td:nth-of-type(2)').should('have.text', 'Second Item'); - cy.contains('button', 'Save').click(); - cy.wait('@editThesauri'); - - cy.contains('span', 'Thesauri').click(); - cy.wait('@fetchThesauri'); + cy.contains('Thesauri added.'); + cy.contains('Dismiss').click(); }); it('should add groups', () => { - cy.intercept('GET', '/api/stats').as('fetchStats'); - cy.contains('a', 'Add thesaurus').click(); - cy.get('#thesauri-name').type('New Thesauri with groups', { delay: 0 }); cy.contains('button', 'Add group').click(); - cy.get('input#group-name').type('First group', { delay: 0 }); + cy.get('input#group-name').type('Group A', { delay: 0 }); + cy.get('input[name="subRows.0.label"]').type('First Child A', { delay: 0 }); + cy.get('input[name="subRows.1.label"]').type('Second Child A', { delay: 0 }); cy.getByTestId('thesaurus-form-submit').click(); + cy.get('tbody tr').should('have.length', 3); cy.contains('button', 'Add group').click(); - cy.get('input#group-name').clear(); - cy.get('input#group-name').type('Second group', { delay: 0 }); + cy.get('input#group-name').type('Group B', { delay: 0 }); + cy.get('input[name="subRows.0.label"]').type('First Child B', { delay: 0 }); cy.getByTestId('thesaurus-form-submit').click(); - cy.get('tbody tr').should('have.length', 2); - cy.get('tbody tr:nth-of-type(1) td:nth-of-type(2)').should('have.text', 'First group'); - cy.get('tbody tr:nth-of-type(2) td:nth-of-type(2)').should('have.text', 'Second group'); - cy.contains('button', 'Save').click(); - cy.wait('@editThesauri'); - cy.wait('@fetchStats'); + cy.get('tbody tr').should('have.length', 4); + saveThesaurus(); }); - it('should edit value', () => { - cy.intercept('GET', '/api/templates').as('fetchTemplates'); - cy.contains('span', 'Thesauri').click(); - cy.wait('@fetchThesauri'); - cy.wait('@fetchTemplates'); - cy.intercept('GET', '/api/stats').as('fetchStats'); - cy.contains('button', 'Edit').click(); - cy.wait('@fetchStats'); - cy.get('#thesauri-name').type(' edited', { delay: 0 }); - cy.contains('button', 'Edit').eq(0).click(); - cy.get('input#item-name').type(' edited', { delay: 0 }); + it('should add items', () => { + cy.contains('button', 'Add item').click(); + cy.get('input[name="newValues.0.label"]').type('Added Root Item', { delay: 0 }); + cy.get('input[name="newValues.1.label"]').type('Added Child Item', { delay: 0 }); + cy.get('select[name="newValues.1.groupId"]').select('Group A'); cy.getByTestId('thesaurus-form-submit').click(); - cy.contains('button', 'Save').click(); - cy.wait('@fetchThesauri'); - cy.get('tbody tr:nth-of-type(1) td:nth-of-type(2)').should('have.text', 'First Item edited'); - cy.wait('@editThesauri'); + cy.get('tbody tr').should('have.length', 5); + saveThesaurus(); }); - it('should edit groups', () => { - cy.intercept('GET', '/api/templates').as('fetchTemplates'); - cy.contains('span', 'Thesauri').click(); - cy.wait('@fetchThesauri'); - cy.wait('@fetchTemplates'); - cy.contains('tr:nth-child(2) button', 'Edit').click(); - cy.get('#thesauri-name').type(' edited', { delay: 0 }); - cy.contains('button', 'Edit').eq(0).click(); - cy.get('input#group-name').type(' edited', { delay: 0 }); + it('should edit an item', () => { + cy.contains('tr', 'Second Item').contains('button', 'Edit').click(); + cy.clearAndType('input[name="newValues.0.label"]', 'Edited Second Item', { delay: 0 }); cy.getByTestId('thesaurus-form-submit').click(); - cy.get('tbody tr:nth-of-type(2) td:nth-of-type(2)').should('have.text', 'First group edited'); - cy.intercept('GET', '/api/dictionaries?_id=*').as('editThesauri'); - cy.contains('button', 'Save').click(); - cy.wait('@editThesauri'); - cy.contains('Thesauri updated.'); + cy.get('tbody tr').should('have.length', 5); + saveThesaurus(); + }); + + it('should edit a group', () => { + cy.contains('tr', 'Group B').contains('button', 'Edit').click(); + cy.clearAndType('input#group-name', 'Edited Group B', { delay: 0 }); + cy.clearAndType('input[name="subRows.0.label"]', 'Edited First Child B', { delay: 0 }); + cy.get('input[name="subRows.1.label"]').type('Added Second Child B', { delay: 0 }); + cy.get('input[name="subRows.2.label"]').type('Added Third Child B', { delay: 0 }); + cy.getByTestId('thesaurus-form-submit').click(); + cy.get('tbody tr').should('have.length', 5); + saveThesaurus(); }); - it('should delete', () => { - cy.intercept('GET', '/api/templates').as('fetchTemplates'); + it('should allow to sort by dnd', () => { + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(3), + cy.get('button[aria-roledescription="sortable"]').eq(0) + ); + saveThesaurus(); + }); + + it('should delete items', () => { + cy.contains('tr', 'Edited Second Item').within(() => cy.get('input[type="checkbox"]').check()); + cy.contains('tr', 'Edited Group B').contains('button', 'Group').click(); + cy.contains('tr', 'Added Second Child B').within(() => + cy.get('input[type="checkbox"]').check() + ); + cy.contains('button', 'Remove').click(); + saveThesaurus(); + }); + it('should use the thesaurus in a template', () => { + cy.contains('a', 'Templates').click(); + cy.contains('a', 'País').click(); + cy.contains('.property-options-list li', 'Select').within(() => { + cy.get('button').click(); + }); + cy.contains('.metadataTemplate-list li', 'Select').within(() => { + cy.contains('Edit').click(); + cy.contains('select', 'Select...').select('New Thesaurus'); + }); + cy.contains('Save').click(); + cy.contains('Saved successfully.'); + }); + + it('should list the thesauri', () => { cy.contains('span', 'Thesauri').click(); - cy.wait('@fetchThesauri'); - cy.wait('@fetchTemplates'); - cy.get('input[type=checkbox]').eq(1).click(); - cy.contains('button', 'Delete').click(); + cy.contains('tr', 'New Thesaurus').contains('País'); + cy.get('tbody').toMatchImageSnapshot(); + }); + + it('should do not allow to delete a used thesaurus', () => { + cy.contains('tr', 'New Thesaurus').within(() => { + cy.get('input[type=checkbox]').should('have.attr', 'disabled'); + }); + }); + + it('should keep sorting', () => { + cy.contains('tr', 'New Thesaurus').contains('button', 'Edit').click(); + cy.contains('tbody', 'Edited Group B'); + cy.get('tbody').toMatchImageSnapshot(); + }); + + it('should do ask for confirmation when delete an item of a used thesaurus', () => { + cy.contains('tr', 'First Item').within(() => cy.get('input[type="checkbox"]').check()); + cy.contains('button', 'Remove').click(); + cy.contains('Are you sure you want to delete this item'); cy.contains('button', 'Accept').click(); - cy.contains('Thesauri deleted'); - cy.get('tbody tr').should('have.length', 1); + saveThesaurus(); }); - it('should not delete when in use', () => { - cy.intercept('GET', 'api/templates').as('fetchtemplates'); - cy.contains('span', 'Templates').click(); - cy.contains('Edit').click(); - cy.get('aside .list-group-item:nth-of-type(3) button').click(); - cy.get('.metadataTemplate li:nth-of-type(3) .property-edit').click(); - cy.get('select').eq(1).select('New Thesauri with groups edited'); - cy.contains('Save').click(); - cy.wait('@fetchtemplates'); + it('should import items from csv', () => { + cy.contains('button', 'Import').click(); + cy.get('input[type=file]').selectFile('./cypress/test_files/thesaurus-test.csv', { + force: true, + }); + cy.contains('Thesauri updated.'); + cy.contains('Dismiss').click(); + cy.get('tbody').toMatchImageSnapshot(); + }); + + it('should sort the items alphabetically', () => { + cy.contains('button', 'Sort').click(); + cy.get('tbody').toMatchImageSnapshot(); + changeLanguage('Español'); + cy.contains('Colores'); + cy.contains('button', 'Ordenar').click(); + cy.get('tbody').toMatchImageSnapshot(); + }); + + it('should cancel the changes', () => { + cy.contains('button', 'Cancelar').click(); + cy.contains('button', 'Descartar cambios').click(); + }); + + it('should use a value when creating an entity', () => { + cy.contains('a', 'Colección').click(); + changeLanguage('English'); + clickOnCreateEntity(); + cy.get('[name="library.sidepanel.metadata.title"]').click(); + cy.get('[name="library.sidepanel.metadata.title"]').type('País select', { delay: 0 }); + cy.contains('select', 'Mecanismo').select('País'); + cy.contains('.form-group.select select', 'Select...').select('Imported Blue'); + cy.contains('button', 'Save').click(); + cy.contains('Entity created'); + cy.contains('.item-document', 'País select').click(); + cy.contains('.metadata-name-select', 'Imported Colors: Imported Blue'); + changeLanguage('Español'); + cy.contains('.documentTypes-selector li', 'País').click(); + cy.contains('.item-document', 'País select').click(); + cy.contains('.metadata-name-select', 'Colores Importados: Azul Importado'); + }); + + it('should update the values in the entities', () => { + changeLanguage('English'); + cy.get('.only-desktop a[aria-label="Settings"]').click(); cy.contains('span', 'Thesauri').click(); - cy.get('tbody tr:nth-of-type(1) input').should('be.disabled'); + cy.contains('tr', 'New Thesaurus').contains('button', 'Edit').click(); + cy.contains('tbody', 'Imported Colors'); + cy.contains('tr', 'Imported Colors').contains('button', 'Edit').click(); + cy.clearAndType('input#group-name', 'Colors', { delay: 0 }); + cy.clearAndType('input[name="subRows.0.label"]', 'Blue', { delay: 0 }); + cy.contains('button', 'Edit group').click(); + saveThesaurus(); + cy.contains('a', 'Library').click(); + cy.contains('.multiselectItem-name', 'País'); + cy.contains('.item-document', 'País select').click(); + cy.contains('.metadata-name-select', 'Colors: Blue'); }); }); diff --git a/cypress/test_files/thesaurus-test.csv b/cypress/test_files/thesaurus-test.csv new file mode 100644 index 0000000000..3fb4f6fecd --- /dev/null +++ b/cypress/test_files/thesaurus-test.csv @@ -0,0 +1,7 @@ +English, Spanish +Item 1, Elemento 1 +Item 2, Elemento 2 +Item 3, Elemento 3 +Imported Colors, Colores Importados +-Imported Blue, -Azul Importado +-Imported Red, -Rojo Importado \ No newline at end of file diff --git a/e2e/regression_suites/settings.test.ts b/e2e/regression_suites/settings.test.ts index a7681da310..1ae1f09c23 100644 --- a/e2e/regression_suites/settings.test.ts +++ b/e2e/regression_suites/settings.test.ts @@ -54,28 +54,6 @@ describe('Settings', () => { }); }); - describe('Thesauri', () => { - it('should display Thesaurus page', async () => { - await selectSettingsPage('Thesauri'); - await testSettingsContent('[data-testid="settings-thesauri"]'); - }); - - it('should display new Thesaurus page', async () => { - await selectSettingsPage('Thesauri'); - await expect(page).toClick('a', { text: 'Add thesaurus' }); - await testSettingsContent('[data-testid="settings-thesauri"]'); - }); - - it('should display new Thesaurus with groups page', async () => { - await selectSettingsPage('Thesauri'); - await expect(page).toClick('a', { text: 'Add thesaurus' }); - await expect(page).toClick('button', { text: 'Add group' }); - await expect(page).toFill('input#group-name', 'Group 1'); - - await testSettingsContent('aside'); - }); - }); - afterAll(async () => { await logout(); }); From 86836874e19e2e34df529870fc51ad79c635cd24 Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 30 Aug 2024 16:35:13 -0300 Subject: [PATCH 33/36] removed previous table and unused dnd component --- .../Layouts/DragAndDrop/Container.tsx | 72 -- .../Layouts/DragAndDrop/DnDDefinitions.ts | 250 ------ .../Layouts/DragAndDrop/DragSource.tsx | 34 - .../Layouts/DragAndDrop/DraggableItem.tsx | 151 ---- .../Layouts/DragAndDrop/DropZone.tsx | 68 -- .../Layouts/DragAndDrop/SortFunction.ts | 95 -- .../Components/Layouts/DragAndDrop/index.ts | 7 - .../DragAndDrop/specs/DragAndDrop.cy.tsx | 277 ------ .../Layouts/DragAndDrop/useDnDContext.tsx | 104 --- .../UI/{TableV2 => Table}/DnDComponents.tsx | 0 .../V2/Components/UI/Table/DraggableRow.tsx | 87 -- .../UI/{TableV2 => Table}/GroupComponents.tsx | 0 .../RowSelectComponents.tsx | 0 .../UI/{TableV2 => Table}/SortingChevrons.tsx | 0 app/react/V2/Components/UI/Table/Table.tsx | 335 ++++--- .../V2/Components/UI/Table/TableBody.tsx | 145 ---- .../V2/Components/UI/Table/TableElements.tsx | 115 --- .../V2/Components/UI/Table/TableHeader.tsx | 37 - app/react/V2/Components/UI/Table/TableRow.tsx | 81 -- .../UI/{TableV2 => Table}/helpers.ts | 0 app/react/V2/Components/UI/Table/index.ts | 2 + .../UI/{TableV2 => Table}/specs/fixtures.ts | 0 .../{TableV2 => Table}/specs/helpers.spec.ts | 0 app/react/V2/Components/UI/TableV2/Table.tsx | 250 ------ app/react/V2/Components/UI/index.ts | 10 +- app/react/V2/Components/UI/specs/Table.cy.tsx | 820 ++++++++++++------ .../V2/Components/UI/specs/TableV2.cy.tsx | 684 --------------- app/react/V2/Components/componentWrappers.tsx | 45 - app/react/stories/DragAndDrop.stories.tsx | 86 -- app/react/stories/Table.stories.tsx | 322 +++++-- app/react/stories/TableV2.stories.tsx | 275 ------ .../dragAndDrop/DragAndDropComponents.tsx | 187 ---- app/react/stories/table/TableComponents.tsx | 198 ----- package.json | 2 - yarn.lock | 9 +- 35 files changed, 1029 insertions(+), 3719 deletions(-) delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/Container.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/index.ts delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx delete mode 100644 app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx rename app/react/V2/Components/UI/{TableV2 => Table}/DnDComponents.tsx (100%) delete mode 100644 app/react/V2/Components/UI/Table/DraggableRow.tsx rename app/react/V2/Components/UI/{TableV2 => Table}/GroupComponents.tsx (100%) rename app/react/V2/Components/UI/{TableV2 => Table}/RowSelectComponents.tsx (100%) rename app/react/V2/Components/UI/{TableV2 => Table}/SortingChevrons.tsx (100%) delete mode 100644 app/react/V2/Components/UI/Table/TableBody.tsx delete mode 100644 app/react/V2/Components/UI/Table/TableElements.tsx delete mode 100644 app/react/V2/Components/UI/Table/TableHeader.tsx delete mode 100644 app/react/V2/Components/UI/Table/TableRow.tsx rename app/react/V2/Components/UI/{TableV2 => Table}/helpers.ts (100%) create mode 100644 app/react/V2/Components/UI/Table/index.ts rename app/react/V2/Components/UI/{TableV2 => Table}/specs/fixtures.ts (100%) rename app/react/V2/Components/UI/{TableV2 => Table}/specs/helpers.spec.ts (100%) delete mode 100644 app/react/V2/Components/UI/TableV2/Table.tsx delete mode 100644 app/react/V2/Components/UI/specs/TableV2.cy.tsx delete mode 100644 app/react/V2/Components/componentWrappers.tsx delete mode 100644 app/react/stories/DragAndDrop.stories.tsx delete mode 100644 app/react/stories/TableV2.stories.tsx delete mode 100644 app/react/stories/dragAndDrop/DragAndDropComponents.tsx delete mode 100644 app/react/stories/table/TableComponents.tsx diff --git a/app/react/V2/Components/Layouts/DragAndDrop/Container.tsx b/app/react/V2/Components/Layouts/DragAndDrop/Container.tsx deleted file mode 100644 index c06d14c00e..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/Container.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { type FC } from 'react'; -import { IDraggable } from 'app/V2/shared/types'; -import { DraggableItem } from './DraggableItem'; -import { DropZone } from './DropZone'; -import type { IDnDContext } from './DnDDefinitions'; - -interface IItemComponentProps { - item: IDraggable; - context: IDnDContext; - index: number; -} - -interface ContainerProps { - context: IDnDContext; - itemComponent?: FC>; - iconHandle?: boolean; - className?: string; - parent?: IDraggable; -} - -/* eslint-disable comma-spacing */ -const Container = ({ - context, - iconHandle = false, - itemComponent, - className, - parent, -}: ContainerProps) => { - const currentItems = parent ? parent.value.items || [] : context.activeItems; - return ( -

- ); -}; - -Container.defaultProps = { - iconHandle: false, - itemComponent: undefined, - className: '', - parent: undefined, -}; - -export type { IItemComponentProps }; -export { Container }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts b/app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts deleted file mode 100644 index 139e1b3deb..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DnDDefinitions.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { Dispatch } from 'react'; -import { get, has, omit } from 'lodash'; -import update from 'immutability-helper'; -import { type IDraggable, ItemTypes } from 'app/V2/shared/types'; -import ID from 'shared/uniqueID'; - -interface IDnDOperations { - getDisplayName: (item: IDraggable) => string; - sortCallback?: Function; - onChange?: (items: T[]) => void; - itemsProperty?: string; - allowEditGroupsWithDnD?: boolean; -} - -interface IDnDContext { - itemsProperty: string; - type: ItemTypes; - addItem: (item: IDraggable) => void; - removeItem: (item: IDraggable) => void; - updateItem: (values: IDraggable) => void; - updateActiveItems: (items: T[]) => IDraggable[]; - sort: Function; - activeItems: IDraggable[]; - availableItems: IDraggable[]; - getDisplayName: (item: IDraggable) => string; - operations: IDnDOperations; - setDragging: React.Dispatch>; -} - -interface IDnDContextState { - activeItems: IDraggable[]; - setActiveItems: React.Dispatch[]>>; - availableItems: IDraggable[]; - setAvailableItems: React.Dispatch[]>>; - itemsProperty: string; - operations: IDnDOperations; -} - -const setIdAndParent = (item: IDraggable, parent?: IDraggable) => { - const idField = has(item.value, 'dndId') ? 'dndId' : 'id'; - const dndId = has(item.value, idField) ? get(item.value, idField) : ID(); - return { ...item, dndId, ...(parent ? { parent } : {}) }; -}; - -const mapWithParent = ( - items: T[], - parent?: IDraggable, - itemsProperty: string = 'items', - allowEditGroupsWithDnD: boolean = true -): IDraggable[] => - items.map(item => { - const draggableItem = { - value: item, - ...(parent === undefined - ? { container: 'root', fixed: !allowEditGroupsWithDnD } - : { fixed: !allowEditGroupsWithDnD }), - } as IDraggable; - const subItems = get(draggableItem.value, itemsProperty); - const itemWithId: IDraggable = setIdAndParent(draggableItem, parent); - if (subItems && subItems.length > 0) { - return { - ...itemWithId, - value: { - ...itemWithId.value, - items: mapWithParent( - subItems as T[], - itemWithId, - itemsProperty, - allowEditGroupsWithDnD - ), - }, - }; - } - return itemWithId; - }) as IDraggable[]; - -const mapWithID = (items: IDraggable[]) => - items.map(item => setIdAndParent(item)); - -const removeChildFromParent = (state: IDnDContextState, newItem: IDraggable) => { - if (newItem.parent) { - const indexOfCurrentParent = state.activeItems.findIndex( - ai => ai.dndId === newItem.parent!.dndId - ); - state.setActiveItems((prevActiveItems: IDraggable[]) => { - const index = prevActiveItems[indexOfCurrentParent].value.items.findIndex( - (ai: IDraggable) => ai.dndId === newItem.dndId - ); - return update(prevActiveItems, { - [indexOfCurrentParent]: { value: { items: { $splice: [[index, 1]] } } }, - }); - }); - } -}; - -const findItemIndex = (items: IDraggable[], searchedItem: IDraggable) => - items.findIndex(item => item.dndId === searchedItem.dndId); - -const removeItemFromList = ( - setActiveItems: Dispatch[]>>, - indexOfNewItem: number -) => { - if (indexOfNewItem > -1) { - setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - $splice: [[indexOfNewItem, 1]], - }) - ); - } -}; - -const addItemToParent = ( - state: IDnDContextState, - indexOfNewItem: number, - newItem: IDraggable, - parent: IDraggable -) => { - removeItemFromList(state.setActiveItems, indexOfNewItem); - state.setActiveItems((prevActiveItems: IDraggable[]) => { - const indexOfParent = findItemIndex(prevActiveItems, parent); - if (indexOfParent > -1) { - return prevActiveItems[indexOfParent].value.items - ? update(prevActiveItems, { - [indexOfParent]: { - value: { - items: { - $push: [ - { - ...omit(newItem, ['parent', 'container', 'value.items']), - parent, - }, - ], - }, - }, - }, - }) - : update(prevActiveItems, { - [indexOfParent]: { - value: { - items: { - $set: [{ ...newItem, parent }], - }, - }, - }, - }); - } - return prevActiveItems; - }); -}; - -const addActiveItem = - (state: IDnDContextState) => - (newItem: IDraggable, parent?: IDraggable) => { - removeChildFromParent(state, newItem); - const indexOfNewItem = findItemIndex(state.activeItems, newItem); - if (parent) { - addItemToParent(state, indexOfNewItem, newItem, parent); - } else if (indexOfNewItem === -1) { - state.setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - //@ts-ignore - $push: [omit(newItem, ['parent', 'container', 'value.items'])], - }) - ); - } - const indexOfSource = findItemIndex(state.availableItems, newItem); - removeItemFromList(state.setAvailableItems, indexOfSource); - }; - -const removeActiveItem = - (state: IDnDContextState) => - (item: IDraggable) => { - if (item.parent !== undefined) { - removeChildFromParent(state, item); - } else { - const index = findItemIndex(state.activeItems, item); - removeItemFromList(state.setActiveItems, index); - } - const availableSubItems: IDraggable[] = (item.value.items || []).map((ai: IDraggable) => - omit(ai, ['parent', 'container']) - ); - const releasedItem = omit(item, ['parent', 'container', 'value.items']); - state.setAvailableItems((prevAvailableItems: IDraggable[]) => - update(prevAvailableItems, { - //@ts-ignore - $push: [releasedItem, ...availableSubItems], - }) - ); - }; - -const sortChildren = ( - state: IDnDContextState, - { - currentItem, - target, - dragIndex, - hoverIndex, - }: { currentItem: IDraggable; target: IDraggable; dragIndex: number; hoverIndex: number } -) => { - const indexOfParent = findItemIndex(state.activeItems, currentItem.parent!); - const targetIndex = findItemIndex(state.activeItems[indexOfParent].value.items || [], target); - if (targetIndex === hoverIndex) { - state.setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - [indexOfParent]: { - value: { - items: { - $splice: [ - [dragIndex, 1], - [hoverIndex, 0, prevActiveItems[indexOfParent].value.items[dragIndex]], - ], - }, - }, - }, - }) - ); - } -}; - -const sortParents = ( - state: IDnDContextState, - { - dragIndex, - hoverIndex, - }: { currentItem: IDraggable; target: IDraggable; dragIndex: number; hoverIndex: number } -) => { - state.setActiveItems((prevActiveItems: IDraggable[]) => - update(prevActiveItems, { - $splice: [ - [dragIndex, 1], - [hoverIndex, 0, prevActiveItems[dragIndex]], - ], - }) - ); -}; -const sortActiveItems = - (state: IDnDContextState) => - (currentItem: IDraggable, target: IDraggable, dragIndex: number, hoverIndex: number) => { - if (currentItem.parent !== undefined) { - sortChildren(state, { currentItem, target, dragIndex, hoverIndex }); - } else { - sortParents(state, { currentItem, target, dragIndex, hoverIndex }); - } - if (state.operations.sortCallback) { - state.operations.sortCallback(state.activeItems.map(item => item.value)); - } - }; - -export type { IDnDContext, IDnDContextState, IDnDOperations }; -export { addActiveItem, removeActiveItem, sortActiveItems, mapWithParent, mapWithID }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx b/app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx deleted file mode 100644 index f18ff3526f..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DragSource.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import { IDraggable } from 'app/V2/shared/types'; -import { DraggableItem } from './DraggableItem'; -import type { IDnDContext } from './DnDDefinitions'; -import { withDnD } from '../../componentWrappers'; - -interface DragSourceComponentProps { - context: IDnDContext; - className?: string; -} -/* eslint-disable comma-spacing */ -const DragSourceComponent = ({ context, className = '' }: DragSourceComponentProps) => ( -
-
    - {context.availableItems.map((item: IDraggable, index: number) => ( - - {context.getDisplayName(item)} - - ))} -
-
-); - -/* eslint-disable comma-spacing */ -const DragSource = (props: DragSourceComponentProps) => - withDnD(DragSourceComponent)(props); - -export { DragSource }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx b/app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx deleted file mode 100644 index a221aad7e1..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DraggableItem.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React, { RefObject, useEffect, useRef } from 'react'; -import type { DragSourceMonitor } from 'react-dnd/dist/types/monitors'; -import { Bars3Icon } from '@heroicons/react/20/solid'; - -import type { IDraggable } from 'app/V2/shared/types'; -import { hoverSortable } from './SortFunction'; -import { IItemComponentProps } from './Container'; -import type { IDnDContext } from './DnDDefinitions'; -import { withDnD } from '../../componentWrappers'; - -interface DraggableItemProps extends React.PropsWithChildren { - item: IDraggable; - useDrag?: Function; - useDrop?: Function; - iconHandle?: boolean; - index: number; - context: any; - className?: string; - omitIcon?: boolean; - wrapperType?: 'li' | 'tr' | 'div'; - container?: string; - previewRef?: RefObject; -} - -type DraggedResult = { - item: IDraggable; - index: number; - container: string; -}; - -/* eslint-disable comma-spacing */ -const hasValidContext = (dropContext?: IDnDContext) => dropContext !== undefined; - -/* eslint-disable comma-spacing */ -const isNotAutoContained = ( - currentItem: IDraggable, - draggedResult: DraggedResult, - dropParent?: { dndId?: string; item?: IDraggable } -) => - (draggedResult.item.container !== currentItem.container || - dropParent === undefined || - dropParent?.dndId !== draggedResult.item.parent?.dndId || - draggedResult.item.container === undefined) && - (dropParent === undefined || draggedResult.item.dndId !== dropParent.dndId); - -/* eslint-disable comma-spacing */ -const hasNoItems = (currentItem: IDraggable) => - currentItem.value.items === undefined || currentItem.value.items.length === 0; - -const getOpacityLevel = (isDragging: boolean) => (isDragging ? 0 : 1); - -const getIconHandleClass = (condition: boolean) => (condition ? 'cursor-move' : ''); - -/* eslint-disable comma-spacing */ -const elementTestId = ( - item: IDraggable, - context: any, - container: string | undefined, - index: number -) => - `${ - (item.parent ? `group_${context.getDisplayName(item.parent)}` : '') || container || 'available' - }-draggable-item-${index}`; - -/* eslint-disable comma-spacing */ -// eslint-disable-next-line max-statements -const DraggableItemComponent = ({ - item, - useDrag = () => {}, - useDrop = () => {}, - iconHandle = false, - index, - children, - context, - className, - omitIcon = false, - wrapperType = 'li', - container, - previewRef, -}: DraggableItemProps) => { - const ref = useRef(null); - const [, drop] = useDrop({ - accept: context.type, - item: { item: { ...item, container }, container, index }, - hover: hoverSortable(ref, { ...item, container }, index, context.setDragging, context.sort), - }); - const [{ isDragging, handlerId }, drag, preview] = useDrag({ - type: context.type, - item: { item: { ...item, container }, index }, - end: (draggedResult: DraggedResult, monitor: DragSourceMonitor) => { - context.setDragging(false); - const { context: dropContext, parent: dropParent } = - monitor.getDropResult & { parent: IDraggable }>() || {}; - - const draggedItem = item.dndId === draggedResult.item.dndId ? draggedResult.item : item; - if ( - hasValidContext(dropContext) && - isNotAutoContained(draggedItem, draggedResult, dropParent) && - hasNoItems(item) && - !draggedResult.item.fixed - ) { - context.addItem(draggedResult.item, dropParent); - } - }, - collect: (monitor: any) => ({ - isDragging: monitor.isDragging(), - handlerId: monitor.getHandlerId(), - }), - }); - - const previewReference = previewRef || ref; - - useEffect(() => { - preview(previewReference); - }, [preview, previewReference]); - - const opacity = getOpacityLevel(isDragging); - - if (previewReference && previewReference.current) { - // eslint-disable-next-line no-param-reassign - previewReference.current.style.opacity = getOpacityLevel(isDragging).toString(); - } - - drag(drop(ref)); - drag(drop(previewReference)); - - const TagName = wrapperType; - - return ( - (item, context, container, index)} - style={{ opacity }} - data-handler-id={handlerId} - key={TagName + item.dndId} - > - {!omitIcon && wrapperType === 'li' && ( - - )} - {children} - - ); -}; - -const DraggableItem = withDnD(DraggableItemComponent); - -export { DraggableItem, hoverSortable }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx b/app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx deleted file mode 100644 index 6150deb6e1..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/DropZone.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { PropsWithChildren, RefObject, useRef } from 'react'; -import type { DropTargetMonitor } from 'react-dnd/dist/types/monitors'; -import { Translate } from 'app/I18N'; -import { IDraggable, ItemTypes } from 'app/V2/shared/types'; -import { withDnD } from '../../componentWrappers'; - -interface DroppableProps extends PropsWithChildren { - name: string; - type: ItemTypes; - context: any; - useDrop?: Function; - parent?: IDraggable; - wrapperType?: 'tbody' | 'div' | 'tr'; - className?: string; - activeClassName?: string; - innerRef?: RefObject; -} - -/* eslint-disable comma-spacing */ -const DropZoneComponent = ({ - name, - useDrop = () => {}, - type, - context, - parent, - children, - wrapperType = 'div', - className, - activeClassName, - innerRef, -}: DroppableProps) => { - const ref = useRef(null); - const [{ canDrop, isOver }, drop] = useDrop(() => ({ - accept: type, - drop: () => ({ name, context, parent }), - collect: (monitor: DropTargetMonitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - })); - - const isActive = canDrop && isOver; - - const classesByTag: { [k: string]: string } = { - div: - className || - 'flex flex-col items-center justify-center text-gray-400 uppercase p-15 h-14 text-base m-5 border border-gray-300 border-dashed rounded-sm cursor-pointer bg-gray-50', - tr: className || '', - 'active-div': - activeClassName || - ' bg-gray-800 dark:bg-gray-700 bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600', - 'active-tr': activeClassName || '', - }; - - const baseClassName = classesByTag[wrapperType] || ''; - const activeClasses = classesByTag[`active-${wrapperType}`] || ''; - const dropClassName = isActive ? baseClassName + activeClasses : className || baseClassName; - - const TagName = wrapperType; - - return ( - - {children || Drag items here} - - ); -}; -const DropZone = withDnD(DropZoneComponent); -export { DropZone }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts b/app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts deleted file mode 100644 index 482cc9cd25..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/SortFunction.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { RefObject } from 'react'; -import type { XYCoord } from 'react-dnd/dist/types/monitors'; -import { IDraggable } from 'app/V2/shared/types'; - -const checkSortArea = ( - monitor: any, - hoverBoundingRect: DOMRect, - dragIndex: number, - hoverIndex: number -) => { - // Get vertical middle - const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - - // Determine mouse position - const clientOffset = monitor.getClientOffset(); - - // Get pixels to the top - const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; - - // Only perform the move when the mouse has crossed half of the items height - // When dragging downwards, only move when the cursor is below 50% - // When dragging upwards, only move when the cursor is above 50% - - // Dragging downwards OR Dragging upwards - if ( - (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) || - (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) - ) { - return true; - } - return false; -}; - -const isOutOfSortArea = ( - ref: RefObject, - monitor: any, - dragIndex: number, - hoverIndex: number -) => - // Determine rectangle on screen - // No hoverBoundingRect AND Don't replace items with themselves - !ref.current || - !ref.current.getBoundingClientRect() || - dragIndex === hoverIndex || - checkSortArea(monitor, ref.current.getBoundingClientRect(), dragIndex, hoverIndex); - -const isNotSortableItem = ( - currentItem: { dndId: string; item: IDraggable }, - target: IDraggable & { ID?: string } -) => - (currentItem.item.parent && !target.parent) || - currentItem.dndId === target.dndId || - currentItem.item.container === undefined; - -const hoverSortable = - ( - ref: RefObject, - target: IDraggable, - index: number, - setDragging: React.Dispatch>, - sortFunction?: Function - ) => - ( - currentItem: { - index: number; - dndId: string; - type: string; - item: IDraggable; - }, - monitor: any - ) => { - setDragging(true); - const dragIndex = currentItem.index; - const hoverIndex = index; - const draggingDown = monitor.getDifferenceFromInitialOffset().y > 0; - const invalidSort = - isNotSortableItem(currentItem, target) || - (draggingDown && dragIndex > hoverIndex && hoverIndex === 0) || - isOutOfSortArea(ref, monitor, dragIndex, hoverIndex); - - if (!ref.current || !sortFunction || !monitor.isOver({ shallow: false }) || invalidSort) { - return; - } - // Time to actually perform the action - sortFunction(currentItem.item, target, dragIndex, hoverIndex); - - // Note: we're mutating the monitor item here! - // Generally it's better to avoid mutations, - // but it's good here for the sake of performance - // to avoid expensive index searches. - // eslint-disable-next-line no-param-reassign - currentItem.index = hoverIndex; - }; - -export { hoverSortable }; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/index.ts b/app/react/V2/Components/Layouts/DragAndDrop/index.ts deleted file mode 100644 index 5f6a7f9c4c..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { DropZone } from './DropZone'; -export type { IItemComponentProps } from './Container'; -export { Container } from './Container'; -export { DraggableItem } from './DraggableItem'; -export { DragSource } from './DragSource'; -export { useDnDContext } from './useDnDContext'; -export type { IDnDContext } from './DnDDefinitions'; diff --git a/app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx b/app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx deleted file mode 100644 index e2c0627dab..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/specs/DragAndDrop.cy.tsx +++ /dev/null @@ -1,277 +0,0 @@ -/* eslint-disable max-statements */ -import React from 'react'; -import 'cypress-axe'; -import { mount } from '@cypress/react18'; -import { composeStories } from '@storybook/react'; -import * as stories from 'app/stories/DragAndDrop.stories'; - -const { Basic, WithItemComponent, Nested, Form } = composeStories(stories); - -describe('DragAndDrop', () => { - it('should be accessible', () => { - cy.checkAccessibility([, , ]); - }); - - const shouldContainListItems = (selector: string, items: string[]) => { - cy.get(selector) - .eq(0) - .within(() => { - cy.get('ul > li') - .should('have.length', items.length) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', items, { timeout: 200 }); - }); - }; - - const dragItem = (name: string, target: string = 'Drag items here') => { - cy.contains(name).trigger('dragstart'); - cy.contains(name).trigger('dragleave'); - cy.contains(target).trigger('drop'); - cy.contains(target).trigger('dragend'); - }; - - describe('Basic', () => { - beforeEach(() => { - mount(); - }); - - it('should list the active items', () => { - shouldContainListItems('div[data-testid="active-bin"]', ['Item 1', 'Item 2', 'Item 3']); - shouldContainListItems('div[data-testid="available-bin"]', ['Item 4', 'Item 5']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 1', 'Item 2', 'Item 3']); - }); - - it('should drag and drop a new item', () => { - dragItem('Item 4'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1', - 'Item 2', - 'Item 3', - 'Item 4', - ]); - shouldContainListItems('div[data-testid="available-bin"]', ['Item 5']); - shouldContainListItems('div[data-testid="state-bin"]', [ - 'Item 1', - 'Item 2', - 'Item 3', - 'Item 4', - ]); - }); - it('should sort the active items from top to down', () => { - cy.get('[data-testid="root-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-2"]', - { target: { x: 100, y: 18 } } - ); - shouldContainListItems('[data-testid="active-bin"]', ['Item 2', 'Item 3', 'Item 1']); - shouldContainListItems('[data-testid="available-bin"]', ['Item 4', 'Item 5']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 2', 'Item 3', 'Item 1']); - }); - - it('should sort the active items from down to top', () => { - cy.get('[data-testid="root-draggable-item-1"]').drag( - '[data-testid="root-draggable-item-0"]', - { target: { x: 100, y: 1 } } - ); - shouldContainListItems('[data-testid="active-bin"]', ['Item 2', 'Item 1', 'Item 3']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 2', 'Item 1', 'Item 3']); - }); - }); - - describe('Item Component', () => { - beforeEach(() => { - mount(); - }); - - it('should remove an active item', () => { - cy.contains('Item 2') - .parent() - .within(() => { - cy.get('button').click(); - }); - dragItem('Item 5'); - shouldContainListItems('div[data-testid="active-bin"]', ['Item 1', 'Item 3', 'Item 5']); - shouldContainListItems('div[data-testid="available-bin"]', ['Item 4', 'Item 2']); - shouldContainListItems('div[data-testid="state-bin"]', ['Item 1', 'Item 3', 'Item 5']); - }); - }); - - describe('Nested Component', () => { - beforeEach(() => { - mount(); - }); - - it('should list nested items', () => { - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - it('should add an Item as a parent', () => { - cy.get('[data-testid="available-draggable-item-0"]').drag('div[data-testid="root"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Item 4\nDRAG ITEMS HERE', - ]); - }); - - it('should add an Item as a child', () => { - cy.get('[data-testid="available-draggable-item-0"]').drag('div[data-testid="group_Item 1"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nItem 4\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 4', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should move an item from a parent to another parent', () => { - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag( - 'div[data-testid="group_Item 2"]' - ); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nDRAG ITEMS HERE', - 'Item 2\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should move an child item to the root level', () => { - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag('div[data-testid="root"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nDRAG ITEMS HERE', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Subitem 1\nDRAG ITEMS HERE', - ]); - }); - - it('should remove a parent', () => { - cy.get('[data-testid="root-draggable-item-0"]').within(() => { - cy.get('button').eq(0).click(); - }); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should add a released item', () => { - cy.get('[data-testid="root-draggable-item-0"]').within(() => { - cy.get('button').eq(0).click(); - }); - cy.get('[data-testid="available-draggable-item-2"]').drag('div[data-testid="root"]'); - - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Item 1\nDRAG ITEMS HERE', - ]); - }); - - it('should remove a child', () => { - cy.get('[data-testid="group_Item 1-draggable-item-0"]').within(() => { - cy.get('button').eq(0).click(); - }); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nDRAG ITEMS HERE', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - - it('should sort parents', () => { - cy.get('[data-testid="root-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-2"]', - { target: { x: 15, y: 135 } } - ); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - ]); - }); - - it('should sort children of a parent', () => { - cy.get('[data-testid="available-draggable-item-1"]').drag('[data-testid="group_Item 1"]'); - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag( - '[data-testid="group_Item 1-draggable-item-1"]', - { - target: { x: 100, y: 30 }, - } - ); - cy.get('[data-testid="root-draggable-item-2"]').drag('div[data-testid="group_Item 1"]', { - target: { x: 100, y: 30 }, - }); - cy.get('[data-testid="available-draggable-item-0"]').drag( - '[data-testid="group_Item 1"]>span', - { - target: { x: 100, y: 10 }, - } - ); - cy.get('[data-testid="group_Item 1-draggable-item-3"]').drag( - '[data-testid="group_Item 1-draggable-item-1"]' - ); - cy.get('[data-testid="group_Item 1-draggable-item-2"]').drag( - '[data-testid="group_Item 1-draggable-item-3"]', - { - target: { x: 100, y: 30 }, - } - ); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nItem 5\nItem 4\nItem 3\nSubitem 1\nDRAG ITEMS HERE', - 'Item 5', - 'Item 4', - 'Item 3', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - ]); - }); - - it('should not change list when dragging is not valid', () => { - cy.get('[data-testid="root-draggable-item-0"]').drag('[data-testid="group_Item 1"]>span', { - target: { x: 100, y: 15 }, - }); - cy.get('[data-testid="group_Item 1-draggable-item-0"]').drag( - '[data-testid="group_Item 1"]>span', - { - target: { x: 100, y: 15 }, - } - ); - cy.get('[data-testid="root-draggable-item-1"]').drag('div[data-testid="root"]'); - shouldContainListItems('div[data-testid="active-bin"]', [ - 'Item 1\nSubitem 1\nDRAG ITEMS HERE', - 'Subitem 1', - 'Item 2\nDRAG ITEMS HERE', - 'Item 3\nDRAG ITEMS HERE', - ]); - }); - }); - - describe('Form', () => { - beforeEach(() => { - mount(); - }); - - it('should update the state of a modified item', () => { - cy.get('[data-testid="root-draggable-item-0"]').within(() => cy.get('input').type(' ALL')); - cy.get('[data-testid="root-draggable-item-1"]').within(() => cy.get('input').type(' VALUES')); - cy.get('[data-testid="root-draggable-item-2"]').within(() => - cy.get('input').type(' WERE UPDATED', { delay: 150 }) - ); - cy.get('div[data-testid="state-bin"]').contains('WERE UPDATED'); - shouldContainListItems('div[data-testid="state-bin"]', [ - 'Item 1 ALL', - 'Item 2 VALUES', - 'Item 3 WERE UPDATED', - ]); - }); - }); -}); diff --git a/app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx b/app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx deleted file mode 100644 index fc858a45c9..0000000000 --- a/app/react/V2/Components/Layouts/DragAndDrop/useDnDContext.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { useEffect, useState } from 'react'; -import update from 'immutability-helper'; -import type { IDraggable } from 'app/V2/shared/types'; -import { ItemTypes } from 'app/V2/shared/types'; -import { omit } from 'lodash'; -import type { IDnDContext, IDnDOperations } from './DnDDefinitions'; -import { - addActiveItem, - removeActiveItem, - mapWithID, - mapWithParent, - sortActiveItems, -} from './DnDDefinitions'; - -/* eslint-disable comma-spacing */ -const useDnDContext = ( - type: ItemTypes, - operations: IDnDOperations, - initialItems: T[] = [], - sourceItems: IDraggable[] = [] -) => { - const [activeItems, setActiveItems] = useState[]>( - mapWithParent( - initialItems, - undefined, - operations.itemsProperty, - operations.allowEditGroupsWithDnD - ) - ); - const [internalChange, setInternalChange] = useState(false); - const [dragging, setDragging] = useState(false); - - const [availableItems, setAvailableItems] = useState[]>( - mapWithID(sourceItems || []) - ); - const itemsProperty = operations.itemsProperty || 'items'; - - const updateItem = (item: IDraggable) => { - setInternalChange(true); - setActiveItems((prevActiveItems: IDraggable[]) => { - const index = prevActiveItems.findIndex(x => x.dndId === item.dndId); - return update(prevActiveItems, { - [index]: { - $set: item, - }, - }); - }); - }; - - useEffect(() => { - if (internalChange === true && !dragging && operations.onChange !== undefined) { - const sortedItems = activeItems - .filter(item => item) - .map(item => { - const values = item.value.items - ? item.value.items.map(subItem => - omit(subItem.value, ['dndId', 'items', operations.itemsProperty || '']) - ) - : []; - return { - ...omit(item.value, ['items', 'dndId', operations.itemsProperty || '']), - ...(operations.itemsProperty && values.length > 0 - ? { [operations.itemsProperty]: values } - : {}), - } as T; - }); - operations.onChange(sortedItems); - } else { - setInternalChange(true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeItems, dragging]); - - const state = { - activeItems, - setActiveItems, - availableItems, - setAvailableItems, - itemsProperty, - operations, - }; - const dndContext: IDnDContext = { - type, - addItem: addActiveItem(state), - removeItem: removeActiveItem(state), - sort: sortActiveItems(state), - updateItem, - updateActiveItems: (items: T[]) => { - const updatedItems = mapWithParent(items, undefined, itemsProperty); - setActiveItems(updatedItems); - setInternalChange(false); - return updatedItems; - }, - activeItems, - availableItems, - getDisplayName: operations.getDisplayName, - itemsProperty, - operations, - setDragging, - }; - return dndContext; -}; - -export { useDnDContext }; diff --git a/app/react/V2/Components/UI/TableV2/DnDComponents.tsx b/app/react/V2/Components/UI/Table/DnDComponents.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/DnDComponents.tsx rename to app/react/V2/Components/UI/Table/DnDComponents.tsx diff --git a/app/react/V2/Components/UI/Table/DraggableRow.tsx b/app/react/V2/Components/UI/Table/DraggableRow.tsx deleted file mode 100644 index c541493878..0000000000 --- a/app/react/V2/Components/UI/Table/DraggableRow.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { PropsWithChildren, RefObject } from 'react'; -import { Row } from '@tanstack/react-table'; -import { IDraggable } from 'app/V2/shared/types'; -import { DraggableItem, DropZone, type IDnDContext } from '../../Layouts/DragAndDrop'; -import { GrabDoubleIcon } from '../../CustomIcons'; - -interface GrabIconProps extends PropsWithChildren { - row: Row; - dndContext: IDnDContext; - previewRef?: RefObject; - item: IDraggable; - highLightGroups?: boolean; - subRowsKey?: string; -} - -interface RowWrapperProps extends PropsWithChildren { - row: Row; - className?: string; - draggableRow?: boolean; - dndContext?: IDnDContext; - innerRef?: RefObject; -} - -// eslint-disable-next-line comma-spacing -const GrabIcon = ({ - dndContext, - row, - previewRef, - item, - subRowsKey, - highLightGroups = true, -}: GrabIconProps) => { - const grabIconColor = - row.getIsExpanded() || - (highLightGroups && row.getCanExpand()) || - (subRowsKey && highLightGroups && Array.isArray((row.original as any)[subRowsKey])) || - row.depth > 0 - ? 'rgb(199 210 254)' - : 'rgb(224 231 255)'; - return ( - - - - ); -}; - -const RowWrapper = - // eslint-disable-next-line comma-spacing - ({ children, dndContext, row, draggableRow, className, innerRef }: RowWrapperProps) => { - if (!draggableRow || !dndContext) { - return ( - - {children} - - ); - } - const notParent = !row.parentId && row.getLeafRows().length === 0; - const parentItem = dndContext.activeItems.find(item => item.dndId === row.parentId); - return ( - - {children} - - ); - }; - -export { RowWrapper, GrabIcon }; diff --git a/app/react/V2/Components/UI/TableV2/GroupComponents.tsx b/app/react/V2/Components/UI/Table/GroupComponents.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/GroupComponents.tsx rename to app/react/V2/Components/UI/Table/GroupComponents.tsx diff --git a/app/react/V2/Components/UI/TableV2/RowSelectComponents.tsx b/app/react/V2/Components/UI/Table/RowSelectComponents.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/RowSelectComponents.tsx rename to app/react/V2/Components/UI/Table/RowSelectComponents.tsx diff --git a/app/react/V2/Components/UI/TableV2/SortingChevrons.tsx b/app/react/V2/Components/UI/Table/SortingChevrons.tsx similarity index 100% rename from app/react/V2/Components/UI/TableV2/SortingChevrons.tsx rename to app/react/V2/Components/UI/Table/SortingChevrons.tsx diff --git a/app/react/V2/Components/UI/Table/Table.tsx b/app/react/V2/Components/UI/Table/Table.tsx index 08d9634864..e02262f5f7 100644 --- a/app/react/V2/Components/UI/Table/Table.tsx +++ b/app/react/V2/Components/UI/Table/Table.tsx @@ -1,159 +1,250 @@ import React, { useEffect, useMemo, useState } from 'react'; import { - getSortedRowModel, - getCoreRowModel, useReactTable, - SortingState, + getCoreRowModel, + ColumnDef, + flexRender, getExpandedRowModel, + SortingState, + getSortedRowModel, + RowSelectionState, } from '@tanstack/react-table'; -import { useIsFirstRender } from 'app/V2/CustomHooks'; -import { TableProps, CheckBoxHeader, CheckBoxCell } from './TableElements'; -import { TableHeader } from './TableHeader'; -import { TableBody } from './TableBody'; - -const applyForSelection = ( - withSelection: any, - withOutSelection: any, - enableSelection: boolean = false -) => (enableSelection ? withSelection : withOutSelection); - -// eslint-disable-next-line comma-spacing, max-statements -const Table = ({ +import { + DragEndEvent, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors, + DndContext, + closestCenter, +} from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; +import { DraggableRow, RowDragHandleCell, DnDHeader } from './DnDComponents'; +import { IndeterminateCheckboxHeader, IndeterminateCheckboxRow } from './RowSelectComponents'; +import { dndSortHandler, getRowIds } from './helpers'; +import { SortingChevrons } from './SortingChevrons'; +import { GroupCell, GroupHeader } from './GroupComponents'; + +type TableRow = { + rowId: string; + disableRowSelection?: boolean; + subRows?: (T & { rowId: string; disableRowSelection?: boolean })[]; +}; + +type TableProps> = { + columns: ColumnDef[]; + data: T[]; + onChange?: ({ + rows, + selectedRows, + sortingState, + }: { + rows: T[]; + selectedRows: RowSelectionState; + sortingState: SortingState; + }) => void; + dnd?: { enable?: boolean; disableEditingGroups?: boolean }; + enableSelections?: boolean; + defaultSorting?: SortingState; + sortingFn?: (sorting: SortingState) => void; + header?: React.ReactNode; + footer?: React.ReactNode; + className?: string; +}; + +const Table = >({ columns, data, - title, + onChange, + dnd, + enableSelections, + defaultSorting, + sortingFn, + header, footer, - initialState, - enableSelection, - sorting, - setSorting, - onSelection, - subRowsKey, - draggableRows = false, - allowEditGroupsWithDnD = true, - highLightGroups = true, - onChange = () => {}, + className, }: TableProps) => { - const manualSorting = Boolean(setSorting); - const [internalSorting, setInternalSortingSorting] = useState( - initialState?.sorting || [] - ); - const [rowSelection, setRowSelection] = useState({}); - const [internalData, setInternalData] = useState(data); - const [sortedChanged, setSortedChanged] = useState(false); - const isFirstRender = useIsFirstRender(); + const [dataState, setDataState] = useState(data); + const [rowSelection, setRowSelection] = useState({}); + const [sortingState, setSortingState] = useState(defaultSorting || []); - useEffect(() => { - setRowSelection({}); - setInternalData(data); - }, [data]); + const rowIds = useMemo(() => getRowIds(dataState), [dataState]); + const { memoizedColumns, groupColumnIndex } = useMemo<{ + memoizedColumns: ColumnDef[]; + groupColumnIndex: number; + // eslint-disable-next-line max-statements + }>(() => { + const tableColumns = [...columns]; + const hasGroups = data.find(item => item.subRows); + let calculatedIndex = 0; - const memoizedColumns = useMemo( - () => [ - ...applyForSelection( - [ - { - ...{ - id: 'checkbox-select', - header: CheckBoxHeader, - cell: CheckBoxCell, - }, - }, - ], - [], - enableSelection - ), - ...columns, - ], - [columns, enableSelection] - ); + if (hasGroups) { + tableColumns.unshift({ + id: 'group-button', + cell: GroupCell, + header: GroupHeader, + meta: { headerClassName: 'w-0' }, + }); + } - const sortingState = manualSorting ? sorting : internalSorting; - const sortingFunction = manualSorting ? setSorting : setInternalSortingSorting; + if (enableSelections) { + calculatedIndex += 1; + tableColumns.unshift({ + id: 'select', + header: IndeterminateCheckboxHeader, + cell: IndeterminateCheckboxRow, + meta: { headerClassName: 'w-0' }, + }); + } + + if (dnd?.enable) { + calculatedIndex += 1; + tableColumns.unshift({ + id: 'drag-handle', + cell: RowDragHandleCell, + header: DnDHeader, + meta: { headerClassName: 'w-0' }, + }); + } + + return { + memoizedColumns: tableColumns, + groupColumnIndex: calculatedIndex, + }; + }, [columns, data, enableSelections, dnd]); const table = useReactTable({ + data: dataState, columns: memoizedColumns, - manualSorting, - data: internalData, - initialState, state: { sorting: sortingState, - ...applyForSelection({ rowSelection }, {}, enableSelection), + ...(rowSelection && { rowSelection }), }, - enableRowSelection: (row: any) => - Boolean(enableSelection && !row.original?.disableRowSelection), - enableSubRowSelection: false, - onRowSelectionChange: applyForSelection(setRowSelection, () => undefined, enableSelection), - onSortingChange: sortingFunction, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getExpandedRowModel: getExpandedRowModel(), - getSubRows: (row: any) => { - if (subRowsKey) { - return row[subRowsKey]; - } - return []; - }, + manualSorting: Boolean(sortingFn), + onSortingChange: setSortingState, + getRowId: row => row.rowId, + getSubRows: row => row.subRows || undefined, + ...(enableSelections && { + //There seems to be a problem with react table types when using a function, typing as any + //fixes the issue + enableRowSelection: (row: any) => row.original.disableRowSelection !== true, + onRowSelectionChange: setRowSelection, + }), }); useEffect(() => { - if (isFirstRender) { - return; - } + setDataState(data); + setRowSelection({}); + }, [data]); - const sorted = table - .getRowModel() - .rows.filter(row => !row.parentId) - .map(row => row.original); - onChange(sorted); - setSortedChanged(false); + useEffect(() => { + if (onChange) { + if (sortingState.length) { + const sortedState = table.getSortedRowModel().rows.map(row => row.original); + onChange({ rows: sortedState, selectedRows: rowSelection, sortingState }); + } else { + onChange({ rows: dataState, selectedRows: rowSelection, sortingState }); + } + } + // 'onChange' and 'table' removed from deps to avoid infinite rerenders // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sortingState]); + }, [dataState, rowSelection, sortingState]); useEffect(() => { - const selectedRows = table.getSelectedRowModel().flatRows; - if (onSelection) { - onSelection(selectedRows); + if (sortingFn) { + sortingFn(sortingState); } - }, [onSelection, rowSelection, table]); + }, [sortingFn, sortingState]); - const handleOnChange = (changedItems: T[]) => { - setSortedChanged(true); - onChange(changedItems); + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (active && over && active.id !== over.id) { + setDataState(() => { + let tableRows = dataState; + if (sortingState.length) { + table.resetSorting(); + tableRows = table.getSortedRowModel().rows.map(row => row.original); + } + return dndSortHandler({ + currentState: tableRows, + dataIds: rowIds, + activeId: active.id, + overId: over.id, + disableEditingGroups: dnd?.disableEditingGroups, + }); + }); + } }; + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ); + return ( -
- - {title && ( - - )} - - - {table.getHeaderGroups().map(headerGroup => ( - - ))} - - -
- {title} -
- {footer &&
{footer}
} -
+ +
+ + {header && } + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(hdr => { + const headerSorting = hdr.column.getCanSort(); + const customClassName = hdr.column.columnDef.meta?.headerClassName; + + return ( + + ); + })} + + ))} + + + + {table.getRowModel().rows.map(row => ( + + ))} + + +
{header}
+ + {flexRender(hdr.column.columnDef.header, hdr.getContext())} + {headerSorting && } + +
+ {footer &&
{footer}
} +
+
); }; +export type { TableProps, TableRow }; export { Table }; diff --git a/app/react/V2/Components/UI/Table/TableBody.tsx b/app/react/V2/Components/UI/Table/TableBody.tsx deleted file mode 100644 index 179348b970..0000000000 --- a/app/react/V2/Components/UI/Table/TableBody.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { PropsWithChildren } from 'react'; -import { Row } from '@tanstack/react-table'; -import { get } from 'lodash'; -import { ItemTypes } from 'app/V2/shared/types'; -import { TableRow } from './TableRow'; -import { withDnD, withDnDBackend } from '../../componentWrappers'; -import { useDnDContext } from '../../Layouts/DragAndDrop'; - -interface TableBodyProps extends PropsWithChildren { - draggableRows: boolean; - allowEditGroupsWithDnD?: boolean; - DndProvider?: React.FC; - HTML5Backend?: any; - items: any; - table: any; - onChange?: any; - subRowsKey?: string; - highLightGroups?: boolean; -} - -type TypeWithDnDId = T & { - dndId: string; -}; - -// eslint-disable-next-line comma-spacing -const setItemId = (item: T, parent: TypeWithDnDId | undefined, index: number) => ({ - ...item, - dndId: parent ? `${parent.dndId}.${index}` : index.toString(), -}); - -const setRowId: ( - subRowsKey: string, - records: T[], - parent?: TypeWithDnDId -) => TypeWithDnDId[] = (subRowsKey, records, parent) => - (records || []) - .filter(f => f) - .map((item, index) => { - const itemWithId = setItemId(item, parent, index); - return { - ...itemWithId, - ...(subRowsKey - ? { - [subRowsKey]: setRowId(subRowsKey, get(item, subRowsKey), itemWithId), - } - : {}), - }; - }); - -// eslint-disable-next-line comma-spacing -const TableBodyComponent = ({ - draggableRows, - allowEditGroupsWithDnD, - DndProvider, - HTML5Backend, - items, - table, - subRowsKey, - onChange, - highLightGroups = true, -}: TableBodyProps) => { - const dndContext = useDnDContext( - ItemTypes.ROW, - { - getDisplayName: item => item.dndId!, - itemsProperty: subRowsKey, - onChange, - allowEditGroupsWithDnD, - }, - items, - [] - ); - - return draggableRows && DndProvider && HTML5Backend ? ( - - - {dndContext.activeItems - .map(item => { - const itemValue = item.value as TypeWithDnDId; - const row = table.getRowModel().rowsById[itemValue.dndId]; - const children = - row && row.getIsExpanded() - ? (item.value.items || []) - .filter(v => v) - .map(subItem => { - const subItemValue = subItem.value as TypeWithDnDId; - const childRow = table.getRowModel().rowsById[subItemValue.dndId]; - - return childRow !== undefined ? ( - - ) : ( - childRow - ); - }) - .filter(child => child !== undefined) - : []; - return row !== undefined ? ( - - - {children} - - ) : ( - row - ); - }) - .filter(row => row !== undefined)} - - - ) : ( - - {table.getRowModel().rows.map((row: Row) => ( - - key={row.id} - row={row} - highLightGroups={highLightGroups} - subRowsKey={subRowsKey} - /> - ))} - - ); -}; - -const typedMemo: (c: T) => T = React.memo; - -const TableBody = (props: TableBodyProps) => { - const tableBodyProps = { ...props, items: setRowId(props.subRowsKey || 'items', props.items) }; - return withDnD(typedMemo(withDnDBackend(TableBodyComponent)))(tableBodyProps); -}; - -export { TableBody }; diff --git a/app/react/V2/Components/UI/Table/TableElements.tsx b/app/react/V2/Components/UI/Table/TableElements.tsx deleted file mode 100644 index 83e2b8c39c..0000000000 --- a/app/react/V2/Components/UI/Table/TableElements.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* eslint-disable react/jsx-props-no-spreading */ -/* eslint-disable react/no-multi-comp */ -import React, { HTMLProps, useEffect, useRef, Dispatch, SetStateAction } from 'react'; -import { - ColumnDef, - TableState, - Row, - Table as TableDef, - SortingState, - Header, -} from '@tanstack/react-table'; -import { ChevronUpDownIcon, ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/20/solid'; -import { t } from 'app/I18N'; - -interface TableProps { - columns: ColumnDef[]; - data: T[]; - title?: string | React.ReactNode; - footer?: string | React.ReactNode; - initialState?: Partial; - enableSelection?: boolean; - sorting?: SortingState; - setSorting?: Dispatch>; - onSelection?: Dispatch[]>>; - subRowsKey?: string; - draggableRows?: boolean; - allowEditGroupsWithDnD?: boolean; - highLightGroups?: boolean; - onChange?: (rows: T[]) => void; -} - -const IndeterminateCheckbox = ({ - indeterminate, - className = '', - id, - disabled, - ...rest -}: { indeterminate?: boolean } & HTMLProps) => { - const ref = useRef(null!); - - useEffect(() => { - if (typeof indeterminate === 'boolean') { - ref.current.indeterminate = !rest.checked && indeterminate; - } - }, [ref, indeterminate, rest.checked]); - - return ( - // eslint-disable-next-line react/jsx-props-no-spreading - - - ); -}; - -// eslint-disable-next-line comma-spacing -const getIcon = (header: Header, sortedChanged: boolean) => { - const sorting = !sortedChanged ? header.column.getIsSorted() : false; - const testId = `${header.id}_${sorting.toString()}`; - switch (sorting) { - case false: - return ; - case 'asc': - return ; - case 'desc': - default: - return ; - } -}; - -// eslint-disable-next-line comma-spacing -const CheckBoxHeader = ({ table }: { table: TableDef }) => ( - -); - -// eslint-disable-next-line comma-spacing -const CheckBoxCell = ({ row }: { row: Row }) => ( -
- { - row.getToggleSelectedHandler()(e); - row.subRows.forEach(subRow => subRow.getToggleSelectedHandler()(e)); - }, - id: row.id, - }} - /> -
-); - -export type { TableProps }; -export { CheckBoxHeader, CheckBoxCell, getIcon }; diff --git a/app/react/V2/Components/UI/Table/TableHeader.tsx b/app/react/V2/Components/UI/Table/TableHeader.tsx deleted file mode 100644 index 3875badaec..0000000000 --- a/app/react/V2/Components/UI/Table/TableHeader.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { HeaderGroup, flexRender } from '@tanstack/react-table'; -import { getIcon } from './TableElements'; - -interface RowProps { - headerGroup: HeaderGroup; - draggableRows: boolean; - sortedChanged: boolean; -} -/* eslint-disable comma-spacing */ -const TableHeader = ({ headerGroup, draggableRows, sortedChanged }: RowProps) => ( - - {headerGroup.headers.map(header => { - const isSortable = header.column.getCanSort(); - const isSelect = header.column.id === 'checkbox-select'; - const commonHeaderClassName = `${draggableRows ? 'pl-7' : ''} ${ - isSelect ? 'w-0 px-4 py-3' : 'px-6 py-3' - }`; - - const headerClassName = `${!header.column.columnDef.meta?.headerClassName?.includes('invisible') ? commonHeaderClassName : ''} ${header.column.columnDef.meta?.headerClassName || ''}`; - - return ( - -
- {flexRender(header.column.columnDef.header, header.getContext())} - {isSortable && getIcon(header, sortedChanged)} -
- - ); - })} - -); - -export { TableHeader }; diff --git a/app/react/V2/Components/UI/Table/TableRow.tsx b/app/react/V2/Components/UI/Table/TableRow.tsx deleted file mode 100644 index a9afbc6cd5..0000000000 --- a/app/react/V2/Components/UI/Table/TableRow.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { PropsWithChildren, useRef } from 'react'; -import { Row, flexRender } from '@tanstack/react-table'; -import { IDraggable } from 'app/V2/shared/types'; -import { type IDnDContext } from '../../Layouts/DragAndDrop'; -import { GrabIcon, RowWrapper } from './DraggableRow'; - -interface TableRowProps extends PropsWithChildren { - row: Row; - draggableRow?: boolean; - dndContext?: IDnDContext; - item?: IDraggable; - highLightGroups?: boolean; - subRowsKey?: string; -} - -/* eslint-disable comma-spacing */ -const TableRow = ({ - draggableRow, - row, - dndContext, - item, - subRowsKey, - highLightGroups = true, -}: TableRowProps) => { - const previewRef = useRef(null); - const icons = - draggableRow && dndContext && item - ? [ - , - ] - : []; - const isSubGroup = row.depth > 0; - const bg = - (row.getIsExpanded() && row.getCanExpand()) || - (highLightGroups && row.getCanExpand()) || - (subRowsKey && highLightGroups && Array.isArray((row.original as any)[subRowsKey])) - ? 'bg-primary-100' - : ''; - - return ( - - {row.getVisibleCells().map((cell, columnIndex) => { - const isSelect = cell.column.id === 'checkbox-select'; - const firstColumnClass = draggableRow && columnIndex === 0 ? 'flex items-center gap-3' : ''; - - let border = ''; - if (isSelect && isSubGroup) { - border = 'border-r-2 border-primary-300'; - } - - return ( - - {icons[columnIndex]} - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ); - })} - - ); -}; - -export { TableRow }; diff --git a/app/react/V2/Components/UI/TableV2/helpers.ts b/app/react/V2/Components/UI/Table/helpers.ts similarity index 100% rename from app/react/V2/Components/UI/TableV2/helpers.ts rename to app/react/V2/Components/UI/Table/helpers.ts diff --git a/app/react/V2/Components/UI/Table/index.ts b/app/react/V2/Components/UI/Table/index.ts new file mode 100644 index 0000000000..eec46d6266 --- /dev/null +++ b/app/react/V2/Components/UI/Table/index.ts @@ -0,0 +1,2 @@ +export { Table } from './Table'; +export type { TableProps, TableRow } from './Table'; diff --git a/app/react/V2/Components/UI/TableV2/specs/fixtures.ts b/app/react/V2/Components/UI/Table/specs/fixtures.ts similarity index 100% rename from app/react/V2/Components/UI/TableV2/specs/fixtures.ts rename to app/react/V2/Components/UI/Table/specs/fixtures.ts diff --git a/app/react/V2/Components/UI/TableV2/specs/helpers.spec.ts b/app/react/V2/Components/UI/Table/specs/helpers.spec.ts similarity index 100% rename from app/react/V2/Components/UI/TableV2/specs/helpers.spec.ts rename to app/react/V2/Components/UI/Table/specs/helpers.spec.ts diff --git a/app/react/V2/Components/UI/TableV2/Table.tsx b/app/react/V2/Components/UI/TableV2/Table.tsx deleted file mode 100644 index e02262f5f7..0000000000 --- a/app/react/V2/Components/UI/TableV2/Table.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { - useReactTable, - getCoreRowModel, - ColumnDef, - flexRender, - getExpandedRowModel, - SortingState, - getSortedRowModel, - RowSelectionState, -} from '@tanstack/react-table'; -import { - DragEndEvent, - KeyboardSensor, - MouseSensor, - TouchSensor, - useSensor, - useSensors, - DndContext, - closestCenter, -} from '@dnd-kit/core'; -import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; -import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; -import { DraggableRow, RowDragHandleCell, DnDHeader } from './DnDComponents'; -import { IndeterminateCheckboxHeader, IndeterminateCheckboxRow } from './RowSelectComponents'; -import { dndSortHandler, getRowIds } from './helpers'; -import { SortingChevrons } from './SortingChevrons'; -import { GroupCell, GroupHeader } from './GroupComponents'; - -type TableRow = { - rowId: string; - disableRowSelection?: boolean; - subRows?: (T & { rowId: string; disableRowSelection?: boolean })[]; -}; - -type TableProps> = { - columns: ColumnDef[]; - data: T[]; - onChange?: ({ - rows, - selectedRows, - sortingState, - }: { - rows: T[]; - selectedRows: RowSelectionState; - sortingState: SortingState; - }) => void; - dnd?: { enable?: boolean; disableEditingGroups?: boolean }; - enableSelections?: boolean; - defaultSorting?: SortingState; - sortingFn?: (sorting: SortingState) => void; - header?: React.ReactNode; - footer?: React.ReactNode; - className?: string; -}; - -const Table = >({ - columns, - data, - onChange, - dnd, - enableSelections, - defaultSorting, - sortingFn, - header, - footer, - className, -}: TableProps) => { - const [dataState, setDataState] = useState(data); - const [rowSelection, setRowSelection] = useState({}); - const [sortingState, setSortingState] = useState(defaultSorting || []); - - const rowIds = useMemo(() => getRowIds(dataState), [dataState]); - const { memoizedColumns, groupColumnIndex } = useMemo<{ - memoizedColumns: ColumnDef[]; - groupColumnIndex: number; - // eslint-disable-next-line max-statements - }>(() => { - const tableColumns = [...columns]; - const hasGroups = data.find(item => item.subRows); - let calculatedIndex = 0; - - if (hasGroups) { - tableColumns.unshift({ - id: 'group-button', - cell: GroupCell, - header: GroupHeader, - meta: { headerClassName: 'w-0' }, - }); - } - - if (enableSelections) { - calculatedIndex += 1; - tableColumns.unshift({ - id: 'select', - header: IndeterminateCheckboxHeader, - cell: IndeterminateCheckboxRow, - meta: { headerClassName: 'w-0' }, - }); - } - - if (dnd?.enable) { - calculatedIndex += 1; - tableColumns.unshift({ - id: 'drag-handle', - cell: RowDragHandleCell, - header: DnDHeader, - meta: { headerClassName: 'w-0' }, - }); - } - - return { - memoizedColumns: tableColumns, - groupColumnIndex: calculatedIndex, - }; - }, [columns, data, enableSelections, dnd]); - - const table = useReactTable({ - data: dataState, - columns: memoizedColumns, - state: { - sorting: sortingState, - ...(rowSelection && { rowSelection }), - }, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getExpandedRowModel: getExpandedRowModel(), - manualSorting: Boolean(sortingFn), - onSortingChange: setSortingState, - getRowId: row => row.rowId, - getSubRows: row => row.subRows || undefined, - ...(enableSelections && { - //There seems to be a problem with react table types when using a function, typing as any - //fixes the issue - enableRowSelection: (row: any) => row.original.disableRowSelection !== true, - onRowSelectionChange: setRowSelection, - }), - }); - - useEffect(() => { - setDataState(data); - setRowSelection({}); - }, [data]); - - useEffect(() => { - if (onChange) { - if (sortingState.length) { - const sortedState = table.getSortedRowModel().rows.map(row => row.original); - onChange({ rows: sortedState, selectedRows: rowSelection, sortingState }); - } else { - onChange({ rows: dataState, selectedRows: rowSelection, sortingState }); - } - } - // 'onChange' and 'table' removed from deps to avoid infinite rerenders - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [dataState, rowSelection, sortingState]); - - useEffect(() => { - if (sortingFn) { - sortingFn(sortingState); - } - }, [sortingFn, sortingState]); - - const handleDragEnd = (event: DragEndEvent) => { - const { active, over } = event; - - if (active && over && active.id !== over.id) { - setDataState(() => { - let tableRows = dataState; - if (sortingState.length) { - table.resetSorting(); - tableRows = table.getSortedRowModel().rows.map(row => row.original); - } - return dndSortHandler({ - currentState: tableRows, - dataIds: rowIds, - activeId: active.id, - overId: over.id, - disableEditingGroups: dnd?.disableEditingGroups, - }); - }); - } - }; - - const sensors = useSensors( - useSensor(MouseSensor, {}), - useSensor(TouchSensor, {}), - useSensor(KeyboardSensor, {}) - ); - - return ( - -
- - {header && } - - - {table.getHeaderGroups().map(headerGroup => ( - - {headerGroup.headers.map(hdr => { - const headerSorting = hdr.column.getCanSort(); - const customClassName = hdr.column.columnDef.meta?.headerClassName; - - return ( - - ); - })} - - ))} - - - - {table.getRowModel().rows.map(row => ( - - ))} - - -
{header}
- - {flexRender(hdr.column.columnDef.header, hdr.getContext())} - {headerSorting && } - -
- {footer &&
{footer}
} -
-
- ); -}; - -export type { TableProps, TableRow }; -export { Table }; diff --git a/app/react/V2/Components/UI/index.ts b/app/react/V2/Components/UI/index.ts index 734d4f78b2..52d73142c6 100644 --- a/app/react/V2/Components/UI/index.ts +++ b/app/react/V2/Components/UI/index.ts @@ -2,7 +2,7 @@ export { Button } from './Button'; export { EmbededButton } from './EmbededButton'; export { Modal } from './Modal'; export { Pill } from './Pill'; -export { Table } from './TableV2/Table'; +export { Table } from './Table'; export { ToggleButton } from './ToggleButton'; export { NotificationsContainer } from './NotificationsContainer'; export { Tabs } from './Tabs'; @@ -17,10 +17,4 @@ export { MediaPlayer } from './MediaPlayer'; export { FileIcon } from './FileIcon'; export type { PillColor } from './Pill'; -export type { TableProps, TableRow } from './TableV2/Table'; - -//This component will be migrated to the new version of the table -// eslint-disable-next-line camelcase -export { Table as Table_deprecated } from './Table/Table'; -// eslint-disable-next-line camelcase -export type { TableProps as TableProps_deprecated } from './Table/TableElements'; +export type { TableProps, TableRow } from './Table'; diff --git a/app/react/V2/Components/UI/specs/Table.cy.tsx b/app/react/V2/Components/UI/specs/Table.cy.tsx index 2a6affaa15..0af68e6484 100644 --- a/app/react/V2/Components/UI/specs/Table.cy.tsx +++ b/app/react/V2/Components/UI/specs/Table.cy.tsx @@ -2,36 +2,35 @@ import React from 'react'; import 'cypress-axe'; import { mount } from '@cypress/react18'; -import { map } from 'lodash'; import { composeStories } from '@storybook/react'; +import { map } from 'lodash'; import * as stories from 'app/stories/Table.stories'; +import { tableWithDisabled } from '../Table/specs/fixtures'; -const { Basic, WithActions, WithCheckboxes, WithInitialState, WithDnD, NestedDnD } = - composeStories(stories); +const { Basic, Nested, Custom } = composeStories(stories); describe('Table', () => { - const data = Basic.args.data || []; + const data = Basic.args.tableData || []; + const dataWithNested = Nested.args.tableData || []; const checkRowContent = (rowNumber: number, cellsContent: string[]) => { - cellsContent.forEach((content, index) => - cy.get(`tbody > :nth-child(${rowNumber}) > :nth-child(${index + 1})`).contains(content) + cellsContent.forEach( + (content, index) => + content && + cy.get(`tbody > :nth-child(${rowNumber}) > :nth-child(${index + 1})`).contains(content) ); }; it('should be accessible', () => { cy.injectAxe(); - mount(); cy.checkA11y(); + }); - mount(); - cy.checkA11y(); - - mount(); - cy.checkA11y(); - - mount(); - cy.checkA11y(); + beforeEach(() => { + Basic.args.defaultSorting = undefined; + Basic.args.enableSelections = false; + Basic.args.dnd = { enable: false, disableEditingGroups: false }; }); it('Should return a table with the columns and row specified', () => { @@ -41,330 +40,645 @@ describe('Table', () => { checkRowContent(1, ['Entity 2', data[0].description, '2']); checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + checkRowContent(3, ['Entity 4', data[2].description, '4']); + checkRowContent(4, ['Entity 3', data[3].description, '3']); + checkRowContent(5, ['Entity 5', data[4].description, '5']); }); - it('Should sort the rows with the sorting state specified', () => { - mount(); - - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 3', data[2].description, '3']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); + it('should render the data in a custom component and styles', () => { + mount(); + cy.get('table').within(() => { + cy.contains('th', 'Description').should( + 'have.attr', + 'class', + 'p-4 text-sm text-gray-500 uppercase border-b bg-blue-700 text-white' + ); + cy.contains('td', 'Entity 2').should( + 'have.attr', + 'class', + 'relative px-4 py-2 bg-gray-100 text-red-700 ' + ); + cy.contains('td', 'Entity 1').should( + 'have.attr', + 'class', + 'relative px-4 py-2 bg-gray-100 text-red-700 ' + ); + cy.get('div[class="text-white bg-orange-500"]').should('have.length', 5); + cy.get('button').should('have.length', 5); + }); }); - it('should render the data in a custom component', () => { - mount(); - cy.get('tbody > :nth-child(1) > :nth-child(3) > div').should( - 'have.class', - 'text-center text-white bg-gray-400 rounded' - ); - }); + it('should trigger the custom action for the buttons', () => { + Custom.args.actionFn = cy.spy().as('actionSpy'); + mount(); - it('should render the header appending custom styles passed in the definition of the columns', () => { - mount(); - cy.get('table > thead > tr > th:nth-child(3)').should( - 'have.class', - 'px-6 py-3 w-1/4 bg-error-100 text-blue-600' - ); + cy.contains('tr', 'Entity 1').within(() => { + cy.contains('button', 'Action').realClick(); + }); + + cy.get('@actionSpy').should('have.been.calledOnceWith', 'A1'); }); describe('Sorting', () => { it('Should be sortable by title', () => { mount(); - cy.get('tr th').contains('Title').click(); + + cy.get('th').contains('Title').realClick(); + cy.contains('button', 'Save changes').realClick(); + checkRowContent(1, ['Entity 1', data[1].description, '1']); checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + checkRowContent(3, ['Entity 3', data[3].description, '3']); + checkRowContent(4, ['Entity 4', data[2].description, '4']); + checkRowContent(5, ['Entity 5', data[4].description, '5']); + }); + + it('should return to the default sorting', () => { + mount(); + cy.get('th').contains('Title').realClick().realClick().realClick(); + + checkRowContent(1, ['Entity 2', data[0].description, '2']); + checkRowContent(2, ['Entity 1', data[1].description, '1']); + checkRowContent(3, ['Entity 4', data[2].description, '4']); + checkRowContent(4, ['Entity 3', data[3].description, '3']); + checkRowContent(5, ['Entity 5', data[4].description, '5']); + }); + + it('should keep selections when sorting', () => { + Basic.args.enableSelections = true; + mount(); + + cy.get('tbody').within(() => { + cy.get('input[type="checkbox"]').eq(0).check(); + cy.get('input[type="checkbox"]').eq(2).check(); + }); + + cy.get('th').contains('Title').realClick().realClick(); + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 2'); + cy.contains('Entity 4'); + }); + }); + + it('should sort items in groups', () => { + mount(); + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + checkRowContent(1, [ + 'Drag row', + 'Select', + 'Group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(2, [ + 'Drag row', + 'Select', + '', + 'Sub 1-1', + dataWithNested[0].subRows[0].description, + '5', + ]); + checkRowContent(3, [ + 'Drag row', + 'Select', + '', + 'Sub 1-2', + dataWithNested[0].subRows[1].description, + '7', + ]); + + cy.get('th').contains('Title').realClick().realClick(); + + checkRowContent(6, [ + 'Drag row', + 'Select', + 'Group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(7, [ + 'Drag row', + 'Select', + '', + 'Sub 1-2', + dataWithNested[0].subRows[1].description, + '7', + ]); + checkRowContent(8, [ + 'Drag row', + 'Select', + '', + 'Sub 1-1', + dataWithNested[0].subRows[0].description, + '5', + ]); }); it('should disable sorting when defined in the columns', () => { - mount(); - cy.get('tr th').contains('Description').click(); - checkRowContent(1, ['Entity 2', '2', data[0].description]); + mount(); + cy.get('tr th').contains('Title').children().should('have.length', 1); + cy.get('tr th').contains('Description').children().should('have.length', 0); + cy.get('tr th').contains('Date added').children().should('have.length', 1); + + cy.get('th').contains('Description').realClick(); + checkRowContent(1, ['Entity 2', data[0].description, '2']); }); - it('should allow external control of sorting', () => { + it('Should sort the rows with the sorting state specified', () => { + Basic.args.defaultSorting = [{ id: 'created', desc: false }]; + mount(); + + checkRowContent(1, ['Entity 1', data[1].description, '1']); + checkRowContent(2, ['Entity 2', data[0].description, '2']); + checkRowContent(3, ['Entity 3', data[3].description, '3']); + }); + + it('should reset sorting state when using dnd after sorting', () => { + Basic.args.dnd = { enable: true }; + mount(); + + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + cy.get('th').contains('Title').realClick().realClick(); + cy.contains('Sorted by title'); + + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(6), + cy.get('button[aria-roledescription="sortable"]').eq(2) + ); + + cy.contains('No sorting'); + }); + + it('it should sort items within groups', () => { + Nested.args.dnd = { enable: false }; + Nested.args.enableSelections = false; + mount(); + + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + checkRowContent(1, ['Open group', 'Group 1', dataWithNested[0].description, '10']); + checkRowContent(2, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); + checkRowContent(3, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); + + cy.contains('th', 'Title').realClick().realClick(); + + checkRowContent(6, ['Open group', 'Group 1', dataWithNested[0].description, '10']); + checkRowContent(7, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); + checkRowContent(8, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); + }); + + it('should allow manually controlling the sorting', () => { const setSortingSpy = cy.stub().as('setSortingSpy'); + Basic.args.sortingFn = setSortingSpy; - mount(); - cy.get('tr th').contains('Title').click(); + mount(); + checkRowContent(1, ['Entity 2', data[0].description, '2']); + cy.get('th').contains('Title').realClick(); + checkRowContent(1, ['Entity 2', data[0].description, '2']); - cy.get('@setSortingSpy').should('have.been.calledOnce'); + cy.get('@setSortingSpy').should('have.been.calledTwice'); + cy.get('@setSortingSpy').should('have.been.calledWith', []); + cy.get('@setSortingSpy').should('have.been.calledWith', [{ id: 'title', desc: false }]); }); }); describe('Selections', () => { - it('should select items from each table', () => { - mount(); - cy.contains('Short text'); - cy.get('[data-testid="table"]').eq(0).get('thead > tr > th').eq(0).click(); - - cy.get('tbody') - .eq(1) - .within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); + beforeEach(() => { + Basic.args.enableSelections = true; + Basic.args.dnd = { enable: true }; + mount(); + }); - cy.contains('p', 'Selected items for Table A: 3'); - cy.contains('p', 'Selections of Table A: Entity 2, Entity 1, Entity 3,'); - cy.contains('p', 'Selected items for Table B: 2'); - cy.contains('p', 'Selections of Table B: Entity 2, Entity 3,'); + it('should select and unselect some items selections', () => { + cy.contains('Select all').realClick(); + cy.get('tbody').within(() => { + cy.get('input[type="checkbox"]').eq(0).uncheck(); + cy.get('input[type="checkbox"]').eq(2).uncheck(); + }); + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 1'); + cy.contains('Entity 2').should('not.exist'); + cy.contains('Entity 3'); + cy.contains('Entity 4').should('not.exist'); + cy.contains('Entity 5'); + }); }); - it('should clear selected items when data changes', () => { - mount(); - cy.contains('Short text'); - cy.get('[data-testid="table"]').eq(0).get('thead > tr > th').eq(0).click(); + it('should reset selections when adding a new entry to the table', () => { + cy.contains('Select all').realClick(); + cy.contains('button', 'Save changes').realClick(); + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 1'); + cy.contains('Entity 2'); + cy.contains('Entity 3'); + cy.contains('Entity 4'); + cy.contains('Entity 5'); + }); + cy.get('#checkbox-header').should('be.checked'); - cy.get('tbody') - .eq(1) - .within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); + cy.contains('button', 'Add new item').realClick(); + cy.get('#checkbox-header').should('not.be.checked'); + }); - cy.contains('button', 'Update table data').click(); + it('should reset selections when removing an item from the table', () => { + cy.contains('Select all').realClick(); + cy.contains('button', 'Save changes').realClick(); + cy.get('#checkbox-header').should('be.checked'); + cy.contains('button', 'Remove last item').realClick(); + cy.get('#checkbox-header').should('not.be.checked'); + }); - cy.contains('p', 'Selections of Table A: Entity 2, Entity 1, Entity 3,'); - cy.contains('p', 'Selections of Table B: Entity 2, Entity 3,').should('not.exist'); + it('should not select items with disabled row selection', () => { + Nested.args.enableSelections = true; + Nested.args.dnd = { enable: true }; + Nested.args.tableData = tableWithDisabled; + mount(); + cy.contains('Select all').realClick(); + cy.contains('button', 'Save changes').realClick(); + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Group 1'); + cy.contains('Group 2'); + cy.contains('Item 1').should('not.exist'); + cy.contains('Item 2'); + }); + cy.get('[data-testid="selected-subrows"]').within(() => { + cy.contains('Sub 1-1').should('not.exist'); + cy.contains('Sub 1-2'); + cy.contains('Sub 1-3'); + }); }); - it('should not clear selections if data is not changed', () => { - mount(); - cy.contains('Short text'); - cy.get('[data-testid="table"]') - .eq(1) - .within(() => cy.get('thead > tr > th').eq(0).click()); + it('should change parent status based on selected children', () => { + Nested.args.enableSelections = true; + Nested.args.dnd = { enable: true }; + Nested.args.tableData = tableWithDisabled; + mount(); - cy.contains('button', 'Reset table data').click(); - cy.contains('p', 'Selections of Table B: Entity 2, Entity 1, Entity 3,'); + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + cy.get('input').realClick().should('be.checked'); + }); + cy.contains('tr', 'Sub 1-1').within(() => { + cy.get('input').should('not.be.checked'); + }); + cy.contains('tr', 'Sub 1-2').within(() => { + cy.get('input').should('be.checked'); + cy.get('input').realClick(); + }); + cy.contains('tr', 'Sub 1-3').within(() => { + cy.get('input').should('be.checked'); + cy.get('input').realClick(); + }); + cy.contains('tr', 'Group 1').within(() => { + cy.get('input').should('be.checked'); + }); }); }); describe('DnD', () => { + beforeEach(() => { + Basic.args.dnd = { enable: true }; + Basic.args.enableSelections = true; + Nested.args.tableData = dataWithNested; + mount(); + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Entity 2 Entity 1 Entity 4 Entity 3 Entity 5'); + }); + }); + it('should sort rows by dragging', () => { - mount(); - cy.get('[data-testid="update_items"] > ul > li').should('have.length', 0); - cy.get('[data-testid="description_desc"]').should('have.length', 1); - - cy.get('[data-testid="root-draggable-item-2"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(1), + cy.get('button[aria-roledescription="sortable"]').eq(0) ); - cy.get('[data-testid="description_false"]').should('have.length', 1); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(3), + cy.get('button[aria-roledescription="sortable"]').eq(2) + ); - checkRowContent(1, ['Entity 3', data[2].description, '3']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); + checkRowContent(1, ['Drag row', 'Select', 'Entity 1', data[1].description, '1']); + checkRowContent(2, ['Drag row', 'Select', 'Entity 2', data[0].description, '2']); + checkRowContent(3, ['Drag row', 'Select', 'Entity 3', data[3].description, '3']); + checkRowContent(4, ['Drag row', 'Select', 'Entity 4', data[2].description, '4']); + checkRowContent(5, ['Drag row', 'Select', 'Entity 5', data[4].description, '5']); - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 3) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 3', 'Entity 2', 'Entity 1']); + cy.contains('button', 'Save changes').realClick(); + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Entity 1 Entity 2 Entity 3 Entity 4 Entity 5'); + }); }); - it('should sort rows by header', () => { - mount(); + it('should keep selections while dragging', () => { + cy.get('tbody').within(() => { + cy.get('input[type="checkbox"]').eq(0).check(); + cy.get('input[type="checkbox"]').eq(2).check(); + }); - cy.get('[data-testid="root-draggable-item-2"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(1), + cy.get('button[aria-roledescription="sortable"]').eq(0) ); - cy.get('[data-testid="title_false"]').click(); + cy.contains('button', 'Save changes').realClick(); - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + cy.get('[data-testid="selected-items"]').within(() => { + cy.contains('Entity 1').should('not.exist'); + cy.contains('Entity 2'); + cy.contains('Entity 4'); + cy.contains('Entity 3').should('not.exist'); + cy.contains('Entity 5').should('not.exist'); + }); - cy.get('[data-testid="title_asc"]').should('have.length', 1); + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Entity 1 Entity 2 Entity 4 Entity 3 Entity 5'); + }); + }); + }); - cy.get('[data-testid="root-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-2"]', - { - target: { x: 5, y: 20 }, - force: true, - } - ); + describe('Nested data', () => { + beforeEach(() => { + Nested.args.dnd = { enable: true }; + Nested.args.enableSelections = true; + mount(); + }); + + it('should check the content', () => { + cy.get('[data-testid="sorted-items"]').within(() => { + cy.contains('Group 1 Group 2 Group 3 Group 4 Item 1 Item 2'); + }); + checkRowContent(1, [ + 'Drag row', + 'Select', + 'Group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(2, [ + 'Drag row', + 'Select', + 'Group', + 'Group 2', + dataWithNested[1].description, + '20', + ]); + checkRowContent(3, [ + 'Drag row', + 'Select', + 'Group', + 'Group 3', + dataWithNested[2].description, + '30', + ]); + checkRowContent(4, [ + 'Drag row', + 'Select', + 'Group', + 'Group 4', + dataWithNested[3].description, + '40', + ]); + checkRowContent(5, [ + 'Drag row', + 'Select', + undefined, + 'Item 1', + dataWithNested[4].description, + '50', + ]); + checkRowContent(6, [ + 'Drag row', + 'Select', + undefined, + 'Item 2', + dataWithNested[5].description, + '60', + ]); + }); - cy.get('[data-testid="title_false"]').should('have.length', 1); + it('should expand groups and check for accessibility', () => { + cy.get('tbody').within(() => { + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('tr', 'Group 2').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('tr', 'Group 3').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('td', 'Sub 1-1'); + cy.contains('td', 'Sub 1-2'); + cy.contains('td', 'Sub 2-1'); + cy.contains('td', 'Sub 2-2'); + cy.contains('td', 'Sub 3-1'); + cy.contains('td', 'Sub 1-2'); + }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 3', data[2].description, '3']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); + cy.checkA11y(); }); - }); - describe('Nested DnD', () => { - it('should render children as subRows', () => { - mount(); + it('should sort children element with dnd', () => { + cy.get('tbody').within(() => { + cy.contains('tr', 'Group 1').within(() => { + cy.contains('Open group').realClick(); + }); + cy.contains('tr', 'Group 3').within(() => { + cy.contains('Open group').realClick(); + }); + cy.contains('td', 'Sub 1-1'); + cy.contains('td', 'Sub 1-2'); + cy.contains('td', 'Sub 3-1'); + cy.contains('td', 'Sub 3-2'); + }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(5), + cy.get('button[aria-roledescription="sortable"]').eq(1) + ); - cy.get('[data-testid="update_items"] > ul > li').should('have.length', 0); + checkRowContent(1, [ + 'Drag row', + 'Select', + 'Open group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(2, [ + 'Drag row', + 'Select', + undefined, + 'Sub 3-1', + dataWithNested[2].subRows[0].description, + '12', + ]); + + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="sorted-subrows"] > .flex > :nth-child(1)').contains( + '|Group 1 - Sub 3-1|' + ); }); - it('should expand a group', () => { - mount(); - - cy.contains('children').click(); + it('should add an item to an empty group', () => { + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + cy.contains('tr', 'Group 3').within(() => { + cy.contains('button', 'Open group').realClick(); + }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(4, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(3), + cy.get('td').contains('Empty group. Drop here to add') + ); - cy.get('[data-testid="update_items"] > ul > li').should('have.length', 0); + checkRowContent(5, [ + 'Drag row', + 'Select', + 'Open group', + 'Group 4', + dataWithNested[3].description, + '40', + ]); + checkRowContent(6, [ + 'Drag row', + 'Select', + undefined, + 'Sub 3-1', + dataWithNested[2].subRows[0].description, + '12', + ]); }); - it('should sort an expanded row', () => { - mount(); - cy.contains('children').click(); + it('should empty a group by dragging all items out of it', () => { + cy.contains('tr', 'Group 1').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); - cy.get('[data-testid="root-draggable-item-1"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(1), + cy.get('button[aria-roledescription="sortable"]').eq(0) + ); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(2), + cy.get('button[aria-roledescription="sortable"]').eq(0) ); - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); + cy.contains('tr', 'Empty group. Drop here to add').should('exist'); - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 3) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 1 Entity a, Entity b', 'Entity 2', 'Entity 3']); - }); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(7), + cy.get('td').contains('Empty group. Drop here to add') + ); - it('should sort an expanded row by the header', () => { - mount(); - cy.contains('children').click(); - cy.get('[data-testid="created_false"]').click(); - cy.contains('children').click(); - checkRowContent(1, ['Entity 3', data[2].description, '3']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 1', data[1].description, '1']); - checkRowContent(4, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(5, ['Entity b', data[1].children![1].description, '5']); + checkRowContent(3, [ + 'Drag row', + 'Select', + 'Open group', + 'Group 1', + dataWithNested[0].description, + '10', + ]); + checkRowContent(4, [ + 'Drag row', + 'Select', + undefined, + 'Item 2', + dataWithNested[5].description, + '60', + ]); + + cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); }); - const checkChildrenSorting = (from: string, to: string, target: { x: number; y: number }) => { - mount(); + it('should not loose selections when dragging into a dropzone', () => { + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); + }); - cy.contains('children').click(); - cy.get(from).drag(to, { - target, - force: true, + cy.contains('tr', 'Item 2').within(() => { + cy.get('input[type="checkbox"]').check(); }); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(4, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); - - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 3) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 2', 'Entity 1 Entity b, Entity a', 'Entity 3']); - }; - - it('should sort children of a group from top to bottom', () => { - checkChildrenSorting( - '[data-testid="group_1-draggable-item-0"]', - '[data-testid="group_1.1"]', - { x: 5, y: 30 } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(5), + cy.get('td').contains('Empty group. Drop here to add') ); + + cy.contains('button', 'Save changes').realClick(); + + cy.get('[data-testid="selected-subrows"]').within(() => { + cy.contains('Item 2'); + }); }); - it('should sort children of a group from bottom to top', () => { - checkChildrenSorting( - '[data-testid="group_1-draggable-item-1"]', - '[data-testid="group_1.0"]', - { x: 5, y: 0 } + it('should disable editing groups with dnd but allow sorting them internally', () => { + Nested.args.dnd = { enable: true, disableEditingGroups: true }; + mount(); + + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); + }); + + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(5), + cy.get('td').contains('Empty group. Drop here to add') ); - }); - it('should move a parent into a group', () => { - mount(); - cy.contains('children').click(); + checkRowContent(5, ['Empty group. Drop here to add']); - cy.get('[data-testid="root-draggable-item-0"]').trigger('dragstart'); - cy.get('[data-testid="root-draggable-item-0"]').trigger('dragleave'); - cy.get('[data-testid="group_1.0"]').trigger('drop', { - target: { x: 5, y: 0 }, + cy.contains('tr', 'Group 2').within(() => { + cy.contains('button', 'Open group').realClick(); }); - cy.get('[data-testid="root-draggable-item-0"]').trigger('dragend'); - cy.contains('children').click(); - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(3, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(4, ['Entity 2', data[0].description, '2']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); - - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 2) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 1 Entity a, Entity b, Entity 2', 'Entity 3']); - }); - it('should move a child outsides a group', () => { - mount(); - cy.contains('children').click(); + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(7), + cy.get('td').contains('Sub 2-1') + ); + + checkRowContent(2, ['Drag row', 'Select', 'Group', 'Group 2']); + checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-1']); + checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-2']); + checkRowContent(9, ['Drag row', 'Select', '', 'Item 2']); - cy.get('[data-testid="group_1-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-0"]', - { - target: { x: 5, y: 0 }, - force: true, - } + cy.realDragAndDrop( + cy.get('button[aria-roledescription="sortable"]').eq(2), + cy.get('td').contains('Sub 2-2') ); - cy.get('[data-testid="group_1-draggable-item-0"]').trigger('dragend'); - cy.contains('children').click(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 3', data[2].description, '3']); - checkRowContent(4, ['Entity a', data[1].children![0].description, '4']); - cy.get('[data-testid="update_items"] > ul > li') - .should('have.length', 4) - .then($els => Cypress.$.makeArray($els).map(el => el.innerText)) - .should('deep.equal', ['Entity 2', 'Entity 1 Entity b', 'Entity 3', 'Entity a']); + checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-2']); + checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-1']); }); - describe('Fixed groups', () => { - it('should not move a child outsides a group if editableGroups is false', () => { - mount(); - cy.contains('children').click(); - - cy.get('[data-testid="group_1-draggable-item-0"]').drag( - '[data-testid="root-draggable-item-1"]', - { - target: { x: 5, y: 0 }, - force: true, - } - ); - cy.get('[data-testid="group_1-draggable-item-0"]').trigger('dragend'); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity a', data[1].children![0].description, '4']); - checkRowContent(4, ['Entity b', data[1].children![1].description, '5']); - checkRowContent(5, ['Entity 3', data[2].description, '3']); + it('should render the correct text for empty groups based on dnd status', () => { + Nested.args.dnd = { enable: false }; + Nested.args.enableSelections = false; + mount(); + + cy.contains('tr', 'Group 4').within(() => { + cy.contains('button', 'Open group').realClick(); }); + + cy.contains('This group is empty'); }); }); }); diff --git a/app/react/V2/Components/UI/specs/TableV2.cy.tsx b/app/react/V2/Components/UI/specs/TableV2.cy.tsx deleted file mode 100644 index 9853271194..0000000000 --- a/app/react/V2/Components/UI/specs/TableV2.cy.tsx +++ /dev/null @@ -1,684 +0,0 @@ -/* eslint-disable max-statements */ -import React from 'react'; -import 'cypress-axe'; -import { mount } from '@cypress/react18'; -import { composeStories } from '@storybook/react'; -import { map } from 'lodash'; -import * as stories from 'app/stories/TableV2.stories'; -import { tableWithDisabled } from '../TableV2/specs/fixtures'; - -const { Basic, Nested, Custom } = composeStories(stories); - -describe('Table', () => { - const data = Basic.args.tableData || []; - const dataWithNested = Nested.args.tableData || []; - - const checkRowContent = (rowNumber: number, cellsContent: string[]) => { - cellsContent.forEach( - (content, index) => - content && - cy.get(`tbody > :nth-child(${rowNumber}) > :nth-child(${index + 1})`).contains(content) - ); - }; - - it('should be accessible', () => { - cy.injectAxe(); - mount(); - cy.checkA11y(); - }); - - beforeEach(() => { - Basic.args.defaultSorting = undefined; - Basic.args.enableSelections = false; - Basic.args.dnd = { enable: false, disableEditingGroups: false }; - }); - - it('Should return a table with the columns and row specified', () => { - mount(); - const toStrings = (cells: JQuery) => map(cells, 'textContent'); - cy.get('tr th').then(toStrings).should('eql', ['Title', 'Description', 'Date added']); - - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 4', data[2].description, '4']); - checkRowContent(4, ['Entity 3', data[3].description, '3']); - checkRowContent(5, ['Entity 5', data[4].description, '5']); - }); - - it('should render the data in a custom component and styles', () => { - mount(); - cy.get('table').within(() => { - cy.contains('th', 'Description').should( - 'have.attr', - 'class', - 'p-4 text-sm text-gray-500 uppercase border-b bg-blue-700 text-white' - ); - cy.contains('td', 'Entity 2').should( - 'have.attr', - 'class', - 'relative px-4 py-2 bg-gray-100 text-red-700 ' - ); - cy.contains('td', 'Entity 1').should( - 'have.attr', - 'class', - 'relative px-4 py-2 bg-gray-100 text-red-700 ' - ); - cy.get('div[class="text-white bg-orange-500"]').should('have.length', 5); - cy.get('button').should('have.length', 5); - }); - }); - - it('should trigger the custom action for the buttons', () => { - Custom.args.actionFn = cy.spy().as('actionSpy'); - mount(); - - cy.contains('tr', 'Entity 1').within(() => { - cy.contains('button', 'Action').realClick(); - }); - - cy.get('@actionSpy').should('have.been.calledOnceWith', 'A1'); - }); - - describe('Sorting', () => { - it('Should be sortable by title', () => { - mount(); - - cy.get('th').contains('Title').realClick(); - cy.contains('button', 'Save changes').realClick(); - - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[3].description, '3']); - checkRowContent(4, ['Entity 4', data[2].description, '4']); - checkRowContent(5, ['Entity 5', data[4].description, '5']); - }); - - it('should return to the default sorting', () => { - mount(); - cy.get('th').contains('Title').realClick().realClick().realClick(); - - checkRowContent(1, ['Entity 2', data[0].description, '2']); - checkRowContent(2, ['Entity 1', data[1].description, '1']); - checkRowContent(3, ['Entity 4', data[2].description, '4']); - checkRowContent(4, ['Entity 3', data[3].description, '3']); - checkRowContent(5, ['Entity 5', data[4].description, '5']); - }); - - it('should keep selections when sorting', () => { - Basic.args.enableSelections = true; - mount(); - - cy.get('tbody').within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); - - cy.get('th').contains('Title').realClick().realClick(); - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 2'); - cy.contains('Entity 4'); - }); - }); - - it('should sort items in groups', () => { - mount(); - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - checkRowContent(1, [ - 'Drag row', - 'Select', - 'Group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(2, [ - 'Drag row', - 'Select', - '', - 'Sub 1-1', - dataWithNested[0].subRows[0].description, - '5', - ]); - checkRowContent(3, [ - 'Drag row', - 'Select', - '', - 'Sub 1-2', - dataWithNested[0].subRows[1].description, - '7', - ]); - - cy.get('th').contains('Title').realClick().realClick(); - - checkRowContent(6, [ - 'Drag row', - 'Select', - 'Group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(7, [ - 'Drag row', - 'Select', - '', - 'Sub 1-2', - dataWithNested[0].subRows[1].description, - '7', - ]); - checkRowContent(8, [ - 'Drag row', - 'Select', - '', - 'Sub 1-1', - dataWithNested[0].subRows[0].description, - '5', - ]); - }); - - it('should disable sorting when defined in the columns', () => { - mount(); - cy.get('tr th').contains('Title').children().should('have.length', 1); - cy.get('tr th').contains('Description').children().should('have.length', 0); - cy.get('tr th').contains('Date added').children().should('have.length', 1); - - cy.get('th').contains('Description').realClick(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - }); - - it('Should sort the rows with the sorting state specified', () => { - Basic.args.defaultSorting = [{ id: 'created', desc: false }]; - mount(); - - checkRowContent(1, ['Entity 1', data[1].description, '1']); - checkRowContent(2, ['Entity 2', data[0].description, '2']); - checkRowContent(3, ['Entity 3', data[3].description, '3']); - }); - - it('should reset sorting state when using dnd after sorting', () => { - Basic.args.dnd = { enable: true }; - mount(); - - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.get('th').contains('Title').realClick().realClick(); - cy.contains('Sorted by title'); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(6), - cy.get('button[aria-roledescription="sortable"]').eq(2) - ); - - cy.contains('No sorting'); - }); - - it('it should sort items within groups', () => { - Nested.args.dnd = { enable: false }; - Nested.args.enableSelections = false; - mount(); - - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - checkRowContent(1, ['Open group', 'Group 1', dataWithNested[0].description, '10']); - checkRowContent(2, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); - checkRowContent(3, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); - - cy.contains('th', 'Title').realClick().realClick(); - - checkRowContent(6, ['Open group', 'Group 1', dataWithNested[0].description, '10']); - checkRowContent(7, [undefined, 'Sub 1-2', dataWithNested[0].subRows[1].description, '7']); - checkRowContent(8, [undefined, 'Sub 1-1', dataWithNested[0].subRows[0].description, '5']); - }); - - it('should allow manually controlling the sorting', () => { - const setSortingSpy = cy.stub().as('setSortingSpy'); - Basic.args.sortingFn = setSortingSpy; - - mount(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - cy.get('th').contains('Title').realClick(); - checkRowContent(1, ['Entity 2', data[0].description, '2']); - - cy.get('@setSortingSpy').should('have.been.calledTwice'); - cy.get('@setSortingSpy').should('have.been.calledWith', []); - cy.get('@setSortingSpy').should('have.been.calledWith', [{ id: 'title', desc: false }]); - }); - }); - - describe('Selections', () => { - beforeEach(() => { - Basic.args.enableSelections = true; - Basic.args.dnd = { enable: true }; - mount(); - }); - - it('should select and unselect some items selections', () => { - cy.contains('Select all').realClick(); - cy.get('tbody').within(() => { - cy.get('input[type="checkbox"]').eq(0).uncheck(); - cy.get('input[type="checkbox"]').eq(2).uncheck(); - }); - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 1'); - cy.contains('Entity 2').should('not.exist'); - cy.contains('Entity 3'); - cy.contains('Entity 4').should('not.exist'); - cy.contains('Entity 5'); - }); - }); - - it('should reset selections when adding a new entry to the table', () => { - cy.contains('Select all').realClick(); - cy.contains('button', 'Save changes').realClick(); - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 1'); - cy.contains('Entity 2'); - cy.contains('Entity 3'); - cy.contains('Entity 4'); - cy.contains('Entity 5'); - }); - cy.get('#checkbox-header').should('be.checked'); - - cy.contains('button', 'Add new item').realClick(); - cy.get('#checkbox-header').should('not.be.checked'); - }); - - it('should reset selections when removing an item from the table', () => { - cy.contains('Select all').realClick(); - cy.contains('button', 'Save changes').realClick(); - cy.get('#checkbox-header').should('be.checked'); - cy.contains('button', 'Remove last item').realClick(); - cy.get('#checkbox-header').should('not.be.checked'); - }); - - it('should not select items with disabled row selection', () => { - Nested.args.enableSelections = true; - Nested.args.dnd = { enable: true }; - Nested.args.tableData = tableWithDisabled; - mount(); - cy.contains('Select all').realClick(); - cy.contains('button', 'Save changes').realClick(); - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Group 1'); - cy.contains('Group 2'); - cy.contains('Item 1').should('not.exist'); - cy.contains('Item 2'); - }); - cy.get('[data-testid="selected-subrows"]').within(() => { - cy.contains('Sub 1-1').should('not.exist'); - cy.contains('Sub 1-2'); - cy.contains('Sub 1-3'); - }); - }); - - it('should change parent status based on selected children', () => { - Nested.args.enableSelections = true; - Nested.args.dnd = { enable: true }; - Nested.args.tableData = tableWithDisabled; - mount(); - - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - cy.get('input').realClick().should('be.checked'); - }); - cy.contains('tr', 'Sub 1-1').within(() => { - cy.get('input').should('not.be.checked'); - }); - cy.contains('tr', 'Sub 1-2').within(() => { - cy.get('input').should('be.checked'); - cy.get('input').realClick(); - }); - cy.contains('tr', 'Sub 1-3').within(() => { - cy.get('input').should('be.checked'); - cy.get('input').realClick(); - }); - cy.contains('tr', 'Group 1').within(() => { - cy.get('input').should('be.checked'); - }); - }); - }); - - describe('DnD', () => { - beforeEach(() => { - Basic.args.dnd = { enable: true }; - Basic.args.enableSelections = true; - Nested.args.tableData = dataWithNested; - mount(); - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Entity 2 Entity 1 Entity 4 Entity 3 Entity 5'); - }); - }); - - it('should sort rows by dragging', () => { - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(1), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(3), - cy.get('button[aria-roledescription="sortable"]').eq(2) - ); - - checkRowContent(1, ['Drag row', 'Select', 'Entity 1', data[1].description, '1']); - checkRowContent(2, ['Drag row', 'Select', 'Entity 2', data[0].description, '2']); - checkRowContent(3, ['Drag row', 'Select', 'Entity 3', data[3].description, '3']); - checkRowContent(4, ['Drag row', 'Select', 'Entity 4', data[2].description, '4']); - checkRowContent(5, ['Drag row', 'Select', 'Entity 5', data[4].description, '5']); - - cy.contains('button', 'Save changes').realClick(); - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Entity 1 Entity 2 Entity 3 Entity 4 Entity 5'); - }); - }); - - it('should keep selections while dragging', () => { - cy.get('tbody').within(() => { - cy.get('input[type="checkbox"]').eq(0).check(); - cy.get('input[type="checkbox"]').eq(2).check(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(1), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-items"]').within(() => { - cy.contains('Entity 1').should('not.exist'); - cy.contains('Entity 2'); - cy.contains('Entity 4'); - cy.contains('Entity 3').should('not.exist'); - cy.contains('Entity 5').should('not.exist'); - }); - - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Entity 1 Entity 2 Entity 4 Entity 3 Entity 5'); - }); - }); - }); - - describe('Nested data', () => { - beforeEach(() => { - Nested.args.dnd = { enable: true }; - Nested.args.enableSelections = true; - mount(); - }); - - it('should check the content', () => { - cy.get('[data-testid="sorted-items"]').within(() => { - cy.contains('Group 1 Group 2 Group 3 Group 4 Item 1 Item 2'); - }); - checkRowContent(1, [ - 'Drag row', - 'Select', - 'Group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(2, [ - 'Drag row', - 'Select', - 'Group', - 'Group 2', - dataWithNested[1].description, - '20', - ]); - checkRowContent(3, [ - 'Drag row', - 'Select', - 'Group', - 'Group 3', - dataWithNested[2].description, - '30', - ]); - checkRowContent(4, [ - 'Drag row', - 'Select', - 'Group', - 'Group 4', - dataWithNested[3].description, - '40', - ]); - checkRowContent(5, [ - 'Drag row', - 'Select', - undefined, - 'Item 1', - dataWithNested[4].description, - '50', - ]); - checkRowContent(6, [ - 'Drag row', - 'Select', - undefined, - 'Item 2', - dataWithNested[5].description, - '60', - ]); - }); - - it('should expand groups and check for accessibility', () => { - cy.get('tbody').within(() => { - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('tr', 'Group 2').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('tr', 'Group 3').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('td', 'Sub 1-1'); - cy.contains('td', 'Sub 1-2'); - cy.contains('td', 'Sub 2-1'); - cy.contains('td', 'Sub 2-2'); - cy.contains('td', 'Sub 3-1'); - cy.contains('td', 'Sub 1-2'); - }); - - cy.checkA11y(); - }); - - it('should sort children element with dnd', () => { - cy.get('tbody').within(() => { - cy.contains('tr', 'Group 1').within(() => { - cy.contains('Open group').realClick(); - }); - cy.contains('tr', 'Group 3').within(() => { - cy.contains('Open group').realClick(); - }); - cy.contains('td', 'Sub 1-1'); - cy.contains('td', 'Sub 1-2'); - cy.contains('td', 'Sub 3-1'); - cy.contains('td', 'Sub 3-2'); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(5), - cy.get('button[aria-roledescription="sortable"]').eq(1) - ); - - checkRowContent(1, [ - 'Drag row', - 'Select', - 'Open group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(2, [ - 'Drag row', - 'Select', - undefined, - 'Sub 3-1', - dataWithNested[2].subRows[0].description, - '12', - ]); - - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="sorted-subrows"] > .flex > :nth-child(1)').contains( - '|Group 1 - Sub 3-1|' - ); - }); - - it('should add an item to an empty group', () => { - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - cy.contains('tr', 'Group 3').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(3), - cy.get('td').contains('Empty group. Drop here to add') - ); - - checkRowContent(5, [ - 'Drag row', - 'Select', - 'Open group', - 'Group 4', - dataWithNested[3].description, - '40', - ]); - checkRowContent(6, [ - 'Drag row', - 'Select', - undefined, - 'Sub 3-1', - dataWithNested[2].subRows[0].description, - '12', - ]); - }); - - it('should empty a group by dragging all items out of it', () => { - cy.contains('tr', 'Group 1').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(1), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(2), - cy.get('button[aria-roledescription="sortable"]').eq(0) - ); - - cy.contains('tr', 'Empty group. Drop here to add').should('exist'); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(7), - cy.get('td').contains('Empty group. Drop here to add') - ); - - checkRowContent(3, [ - 'Drag row', - 'Select', - 'Open group', - 'Group 1', - dataWithNested[0].description, - '10', - ]); - checkRowContent(4, [ - 'Drag row', - 'Select', - undefined, - 'Item 2', - dataWithNested[5].description, - '60', - ]); - - cy.contains('tr', 'Empty group. Drop here to add').should('not.exist'); - }); - - it('should not loose selections when dragging into a dropzone', () => { - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.contains('tr', 'Item 2').within(() => { - cy.get('input[type="checkbox"]').check(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(5), - cy.get('td').contains('Empty group. Drop here to add') - ); - - cy.contains('button', 'Save changes').realClick(); - - cy.get('[data-testid="selected-subrows"]').within(() => { - cy.contains('Item 2'); - }); - }); - - it('should disable editing groups with dnd but allow sorting them internally', () => { - Nested.args.dnd = { enable: true, disableEditingGroups: true }; - mount(); - - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(5), - cy.get('td').contains('Empty group. Drop here to add') - ); - - checkRowContent(5, ['Empty group. Drop here to add']); - - cy.contains('tr', 'Group 2').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(7), - cy.get('td').contains('Sub 2-1') - ); - - checkRowContent(2, ['Drag row', 'Select', 'Group', 'Group 2']); - checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-1']); - checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-2']); - checkRowContent(9, ['Drag row', 'Select', '', 'Item 2']); - - cy.realDragAndDrop( - cy.get('button[aria-roledescription="sortable"]').eq(2), - cy.get('td').contains('Sub 2-2') - ); - - checkRowContent(3, ['Drag row', 'Select', '', 'Sub 2-2']); - checkRowContent(4, ['Drag row', 'Select', '', 'Sub 2-1']); - }); - - it('should render the correct text for empty groups based on dnd status', () => { - Nested.args.dnd = { enable: false }; - Nested.args.enableSelections = false; - mount(); - - cy.contains('tr', 'Group 4').within(() => { - cy.contains('button', 'Open group').realClick(); - }); - - cy.contains('This group is empty'); - }); - }); -}); diff --git a/app/react/V2/Components/componentWrappers.tsx b/app/react/V2/Components/componentWrappers.tsx deleted file mode 100644 index fe17e7eaf4..0000000000 --- a/app/react/V2/Components/componentWrappers.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; - -/* eslint-disable comma-spacing */ -const withLazy = - (Component: React.FC, moduleImport: Function, extractor: (module: unknown) => {}) => - (props: T & { key?: string }) => { - const lazyModuleRef = useRef({}); - const [isLoaded, setIsLoaded] = useState(false); - - useEffect(() => { - moduleImport().then((module: unknown) => { - lazyModuleRef.current = extractor(module); - setIsLoaded(true); - return module; - }); - }, []); - - const componentProps = { ...lazyModuleRef.current, ...props }; - - return isLoaded ? : null; - }; - -/* eslint-disable comma-spacing */ -const withDnD = (Component: React.FC) => - withLazy( - Component, - async () => import('react-dnd'), - (module: any) => ({ - useDrag: module.useDrag, - useDrop: module.useDrop, - useDragDropManager: module.useDragDropManager, - DndProvider: module.DndProvider, - }) - ); - -/* eslint-disable comma-spacing */ -const withDnDBackend = (Component: React.FC) => - withLazy( - Component, - async () => import('react-dnd-html5-backend'), - (module: any) => ({ - HTML5Backend: module.HTML5Backend, - }) - ); -export { withDnD, withDnDBackend }; diff --git a/app/react/stories/DragAndDrop.stories.tsx b/app/react/stories/DragAndDrop.stories.tsx deleted file mode 100644 index 176e8513aa..0000000000 --- a/app/react/stories/DragAndDrop.stories.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { PropsWithChildren } from 'react'; -import { DndProvider } from 'react-dnd'; -import { Provider } from 'react-redux'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { Meta, StoryObj } from '@storybook/react'; -import { ItemTypes } from 'app/V2/shared/types'; -import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers'; -import { - CardWithDnD, - CardWithRemove, - DnDClient, - DnDClientWithForm, -} from './dragAndDrop/DragAndDropComponents'; - -const meta: Meta = { - title: 'Components/DragAndDrop', - component: DnDClient, -}; - -type Story = StoryObj; - -const RenderWithProvider = ({ children }: PropsWithChildren) => ( - - {children} - -); - -const Primary: Story = { - render: args => ( - - - - ), -}; - -const WithForm: Story = { - render: args => ( - - - - ), -}; - -const Basic = { - ...Primary, - args: { - type: ItemTypes.BOX, - items: [{ name: 'Item 1' }, { name: 'Item 2' }, { name: 'Item 3' }], - iconHandle: true, - }, -}; - -const WithItemComponent = { - ...Primary, - args: { - ...Basic.args, - itemComponent: CardWithRemove, - }, -}; - -const Nested = { - ...Primary, - args: { - ...Basic.args, - iconHandle: false, - items: [ - { name: 'Item 1', items: [{ name: 'Subitem 1' }] }, - { name: 'Item 2', items: [] }, - { name: 'Item 3', items: [] }, - ], - itemComponent: CardWithDnD, - }, -}; - -const Form = { - ...WithForm, - args: { - type: ItemTypes.BOX, - items: [{ name: 'Item 1' }, { name: 'Item 2' }, { name: 'Item 3' }], - iconHandle: true, - }, -}; - -export { Basic, WithItemComponent, Nested, Form }; -export default meta; diff --git a/app/react/stories/Table.stories.tsx b/app/react/stories/Table.stories.tsx index 96add38582..5e30e16323 100644 --- a/app/react/stories/Table.stories.tsx +++ b/app/react/stories/Table.stories.tsx @@ -1,109 +1,275 @@ -import React from 'react'; +/* eslint-disable max-lines */ +import React, { useRef, useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; -import { Table_deprecated as Table } from 'V2/Components/UI'; -import { - StoryComponent, - type SampleSchema, - CheckboxesTableComponent, - basicColumns, - withActionsColumns, -} from './table/TableComponents'; - -const meta: Meta = { - title: 'Components/Table', - component: Table, +import { action } from '@storybook/addon-actions'; +import { Cell, createColumnHelper, SortingState } from '@tanstack/react-table'; +import { Provider } from 'react-redux'; +import { Button, Table } from 'V2/Components/UI'; +import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers'; +import { BasicData, DataWithGroups, basicData, dataWithGroups } from './table/fixtures'; + +type StoryProps = { + columnType: string; + tableData: any[]; + dnd?: { enable?: boolean; disableEditingGroups?: boolean }; + enableSelections?: boolean; + defaultSorting?: SortingState; + sortingFn?: () => void; + actionFn?: () => void; }; -type Story = StoryObj> & { args?: { showUpdates?: boolean } }; +const CustomDateCell = ({ cell }: { cell: Cell }) => ( +
{cell.renderValue()}
+); -const Primary: Story = { - render: args => , +const ActionHeader = () => Actions; + +const ActionCell = ({ cell }: { cell: Cell }) => { + const actionFn = cell.getContext().column.columnDef.meta?.action + ? cell.getContext().column.columnDef.meta?.action! + : () => {}; + + return ( + + ); }; -const Checkboxes: Story = { - render: CheckboxesTableComponent, +const basicColumnHelper = createColumnHelper(); +const nestedColumnHelper = createColumnHelper(); + +const basicColumns = [ + basicColumnHelper.accessor('title', { header: 'Title' }), + basicColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), + nestedColumnHelper.accessor('created', { header: 'Date added' }), +]; + +const nestedColumns = [ + nestedColumnHelper.accessor('title', { header: 'Title' }), + nestedColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), + nestedColumnHelper.accessor('created', { header: 'Date added' }), +]; + +const getCustomColums = (actionFn?: () => any) => [ + basicColumnHelper.accessor('title', { + header: 'Title', + meta: { contentClassName: 'bg-gray-100 text-red-700' }, + }), + basicColumnHelper.accessor('description', { + enableSorting: false, + header: 'Description', + size: 200, + meta: { headerClassName: 'bg-blue-700 text-white' }, + }), + basicColumnHelper.accessor('created', { header: 'Date added', cell: CustomDateCell }), + basicColumnHelper.display({ + id: 'action', + header: ActionHeader, + cell: ActionCell, + minSize: 25, + size: 0, + meta: { action: actionFn || action('accepted') }, + }), +]; + +const getColumns = (type: string, actionFn?: () => any) => { + switch (type) { + case 'nested': + return nestedColumns; + + case 'custom': + return getCustomColums(actionFn); + + default: + return basicColumns; + } }; -const Basic: Story = { - ...Primary, - args: { - title: 'Table name', - footer: * Table footer, - columns: basicColumns, - data: [ - { title: 'Entity 2', created: 2, description: 'Short text' }, - { - title: 'Entity 1', - created: 1, - description: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus vel efficitur quam. Donec feugiat at libero at rutrum.', - children: [ - { - title: 'Entity a', - created: 4, - description: 'Donec feugiat at libero at rutrum.', - }, - { - title: 'Entity b', - created: 5, - description: 'Phasellus vel efficitur quam.', - }, - ], - }, - { - title: 'Entity 3', - created: 3, - description: 'Morbi congue et justo vitae congue. Vivamus porttitor et leo vitae efficitur', - }, - ], - setSorting: undefined, - }, +const StoryComponent = ({ + columnType, + tableData, + dnd, + enableSelections, + defaultSorting, + sortingFn, + actionFn, +}: StoryProps) => { + const [dataState, setDataState] = useState(tableData); + const [selected, setSelected] = useState({}); + const [sorting, setSorting] = useState([]); + const currentDataState = useRef(tableData); + const currentSelections = useRef({}); + const [itemCounter, setItemCounter] = useState(1); + + const columns = getColumns(columnType, actionFn); + + return ( +
+
+ { + currentDataState.current = rows; + currentSelections.current = selectedRows; + setSorting(sortingState); + }} + sortingFn={sortingFn} + dnd={dnd} + enableSelections={enableSelections} + header={ +
+

Table heading

+

{sorting.length ? `Sorted by ${sorting[0].id}` : 'No sorting'}

+
+ } + footer={

My table footer

} + /> +
+ + + + +
+ +
+
+

Row state:

+
{dataState.map(ds => `${ds.title} `)}
+
+
+
+

Subrow state:

+
+ {dataState.map((ds: DataWithGroups) => + ds.subRows?.map(subRow => ( + + |{ds.title} - {subRow.title}| + + )) + )} +
+
+
+
+

Selected rows:

+
+ {dataState + .filter(ds => ds.rowId in selected) + .map(ds => ( + {ds.title} + ))} +
+
+
+
+

Selected subRows:

+
+ {dataState.map((ds: DataWithGroups) => + ds.subRows + ?.filter(subRow => subRow.rowId in selected) + .map(subRow => {subRow.title}) + )} +
+
+ + ); }; -const WithInitialState: Story = { - ...Primary, - args: { - ...Basic.args, - initialState: { sorting: [{ id: 'description', desc: true }] }, - }, +const meta: Meta = { + title: 'Components/TableV2', + component: StoryComponent, }; -const WithActions: Story = { - ...Primary, - args: { - ...Basic.args, - columns: withActionsColumns, - }, +type Story = StoryObj; + +const Primary: Story = { + render: args => ( + + + + ), }; -const WithCheckboxes = { - ...Checkboxes, +const Basic = { + ...Primary, args: { - ...Basic.args, + dnd: { enable: true, disableEditingGroups: false }, + enableSelections: true, + defaultSorting: undefined, + tableData: basicData, + columnType: 'basic', + sortingFn: undefined, + actionFn: undefined, }, }; -const WithDnD: Story = { +const Nested = { ...Primary, args: { ...Basic.args, - draggableRows: true, - initialState: { sorting: [{ id: 'description', desc: true }] }, - showUpdates: true, + tableData: dataWithGroups, + columnType: 'nested', }, }; -const NestedDnD: Story = { +const Custom = { ...Primary, args: { ...Basic.args, - subRowsKey: 'children', - showUpdates: true, - draggableRows: true, - allowEditGroupsWithDnD: true, + enableSelections: false, + dnd: undefined, + columnType: 'custom', }, }; -export { Basic, WithActions, WithCheckboxes, WithInitialState, WithDnD, NestedDnD }; - +export { Basic, Nested, Custom }; export default meta; diff --git a/app/react/stories/TableV2.stories.tsx b/app/react/stories/TableV2.stories.tsx deleted file mode 100644 index 5e30e16323..0000000000 --- a/app/react/stories/TableV2.stories.tsx +++ /dev/null @@ -1,275 +0,0 @@ -/* eslint-disable max-lines */ -import React, { useRef, useState } from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import { action } from '@storybook/addon-actions'; -import { Cell, createColumnHelper, SortingState } from '@tanstack/react-table'; -import { Provider } from 'react-redux'; -import { Button, Table } from 'V2/Components/UI'; -import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers'; -import { BasicData, DataWithGroups, basicData, dataWithGroups } from './table/fixtures'; - -type StoryProps = { - columnType: string; - tableData: any[]; - dnd?: { enable?: boolean; disableEditingGroups?: boolean }; - enableSelections?: boolean; - defaultSorting?: SortingState; - sortingFn?: () => void; - actionFn?: () => void; -}; - -const CustomDateCell = ({ cell }: { cell: Cell }) => ( -
{cell.renderValue()}
-); - -const ActionHeader = () => Actions; - -const ActionCell = ({ cell }: { cell: Cell }) => { - const actionFn = cell.getContext().column.columnDef.meta?.action - ? cell.getContext().column.columnDef.meta?.action! - : () => {}; - - return ( - - ); -}; - -const basicColumnHelper = createColumnHelper(); -const nestedColumnHelper = createColumnHelper(); - -const basicColumns = [ - basicColumnHelper.accessor('title', { header: 'Title' }), - basicColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), - nestedColumnHelper.accessor('created', { header: 'Date added' }), -]; - -const nestedColumns = [ - nestedColumnHelper.accessor('title', { header: 'Title' }), - nestedColumnHelper.accessor('description', { header: 'Description', enableSorting: false }), - nestedColumnHelper.accessor('created', { header: 'Date added' }), -]; - -const getCustomColums = (actionFn?: () => any) => [ - basicColumnHelper.accessor('title', { - header: 'Title', - meta: { contentClassName: 'bg-gray-100 text-red-700' }, - }), - basicColumnHelper.accessor('description', { - enableSorting: false, - header: 'Description', - size: 200, - meta: { headerClassName: 'bg-blue-700 text-white' }, - }), - basicColumnHelper.accessor('created', { header: 'Date added', cell: CustomDateCell }), - basicColumnHelper.display({ - id: 'action', - header: ActionHeader, - cell: ActionCell, - minSize: 25, - size: 0, - meta: { action: actionFn || action('accepted') }, - }), -]; - -const getColumns = (type: string, actionFn?: () => any) => { - switch (type) { - case 'nested': - return nestedColumns; - - case 'custom': - return getCustomColums(actionFn); - - default: - return basicColumns; - } -}; - -const StoryComponent = ({ - columnType, - tableData, - dnd, - enableSelections, - defaultSorting, - sortingFn, - actionFn, -}: StoryProps) => { - const [dataState, setDataState] = useState(tableData); - const [selected, setSelected] = useState({}); - const [sorting, setSorting] = useState([]); - const currentDataState = useRef(tableData); - const currentSelections = useRef({}); - const [itemCounter, setItemCounter] = useState(1); - - const columns = getColumns(columnType, actionFn); - - return ( -
-
-
{ - currentDataState.current = rows; - currentSelections.current = selectedRows; - setSorting(sortingState); - }} - sortingFn={sortingFn} - dnd={dnd} - enableSelections={enableSelections} - header={ -
-

Table heading

-

{sorting.length ? `Sorted by ${sorting[0].id}` : 'No sorting'}

-
- } - footer={

My table footer

} - /> -
- - - - -
- -
-
-

Row state:

-
{dataState.map(ds => `${ds.title} `)}
-
-
-
-

Subrow state:

-
- {dataState.map((ds: DataWithGroups) => - ds.subRows?.map(subRow => ( - - |{ds.title} - {subRow.title}| - - )) - )} -
-
-
-
-

Selected rows:

-
- {dataState - .filter(ds => ds.rowId in selected) - .map(ds => ( - {ds.title} - ))} -
-
-
-
-

Selected subRows:

-
- {dataState.map((ds: DataWithGroups) => - ds.subRows - ?.filter(subRow => subRow.rowId in selected) - .map(subRow => {subRow.title}) - )} -
-
- - ); -}; - -const meta: Meta = { - title: 'Components/TableV2', - component: StoryComponent, -}; - -type Story = StoryObj; - -const Primary: Story = { - render: args => ( - - - - ), -}; - -const Basic = { - ...Primary, - args: { - dnd: { enable: true, disableEditingGroups: false }, - enableSelections: true, - defaultSorting: undefined, - tableData: basicData, - columnType: 'basic', - sortingFn: undefined, - actionFn: undefined, - }, -}; - -const Nested = { - ...Primary, - args: { - ...Basic.args, - tableData: dataWithGroups, - columnType: 'nested', - }, -}; - -const Custom = { - ...Primary, - args: { - ...Basic.args, - enableSelections: false, - dnd: undefined, - columnType: 'custom', - }, -}; - -export { Basic, Nested, Custom }; -export default meta; diff --git a/app/react/stories/dragAndDrop/DragAndDropComponents.tsx b/app/react/stories/dragAndDrop/DragAndDropComponents.tsx deleted file mode 100644 index 290ff34410..0000000000 --- a/app/react/stories/dragAndDrop/DragAndDropComponents.tsx +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { FC, useCallback } from 'react'; -import { TrashIcon } from '@heroicons/react/20/solid'; -import { Button } from 'app/V2/Components/UI'; -import { - Container, - DragSource, - DraggableItem, - DropZone, - IItemComponentProps, - useDnDContext, - IDnDContext, -} from 'app/V2/Components/Layouts/DragAndDrop'; -import type { IDraggable } from 'app/V2/shared/types'; -import debounce from 'app/utils/debounce'; - -interface DnDValueExample { - name: string; - items?: DnDValueExample[]; -} - -const sampleDefaultName = (item: IDraggable) => item.value.name; - -const sourceItems: IDraggable[] = [ - { value: { name: 'Item 4' } }, - { value: { name: 'Item 5' } }, -]; - -const CardWithRemove: FC> = ({ item, context }) => ( -
-
{context.getDisplayName(item)}
- -
-); - -const CardWithDnD: FC> = ({ item, context, index }) => ( -
- - -
-); - -const DndContextState = ({ - activeItems, - context, - child = false, -}: { - activeItems: IDraggable[]; - context: IDnDContext; - child?: boolean; -}) => ( -
-
-

State Items

-
    - {activeItems.map((item: IDraggable) => ( -
  • - {context.getDisplayName(item)} - {item.value.items && ( - v)} - child - /> - )} -
  • - ))} -
-
-
-); -const DnDClient = ({ items, type, itemComponent }: any) => { - const dndContext = useDnDContext(type, { getDisplayName: sampleDefaultName }, items, sourceItems); - - return ( -
-
-
-

Active Items

- -
-
-
-

Available Items

- className="p-3 mb-2 text-sm" context={dndContext} /> -
-
-
- -
- ); -}; - -const EditableItem = ({ - dndContext, - item, - index, -}: { - dndContext: IDnDContext; - item: IDraggable; - index: number; -}) => { - const handleChange = debounce((e: React.ChangeEvent) => { - dndContext.updateItem({ ...item, value: { ...item.value, name: e.target.value } }); - }, 300); - - const debouncedChangeHandler = useCallback(handleChange, [handleChange]); - - return ( - - ); -}; -const DnDClientWithForm = ({ items, type }: any) => { - const dndContext = useDnDContext(type, { getDisplayName: sampleDefaultName }, items, sourceItems); - - return ( - <> -
-
-
-

Active Items

-
    - {dndContext.activeItems.map((item: IDraggable, index: number) => ( - - - - ))} -
- -
-
-
-

Available Items

- -
-
-
-
- - - ); -}; - -export type { DnDValueExample }; -export { - DnDClientWithForm, - DnDClient, - CardWithDnD, - CardWithRemove, - sourceItems, - sampleDefaultName, -}; diff --git a/app/react/stories/table/TableComponents.tsx b/app/react/stories/table/TableComponents.tsx deleted file mode 100644 index 7ddcab4246..0000000000 --- a/app/react/stories/table/TableComponents.tsx +++ /dev/null @@ -1,198 +0,0 @@ -/* eslint-disable react/no-multi-comp */ -import React, { useState } from 'react'; -import { CellContext, createColumnHelper, Row } from '@tanstack/react-table'; -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; -import { - EmbededButton, - Table_deprecated as Table, - TableProps_deprecated as TableProps, -} from 'V2/Components/UI'; -import { Button } from 'V2/Components/UI/Button'; -import { get } from 'lodash'; - -type SampleSchema = { - title: string; - description: string; - created: number; - children?: SampleSchema[]; -}; - -const columnHelper = createColumnHelper(); -const CustomCell = ({ cell }: CellContext) => ( -
{cell.getValue()}
-); - -const ActionsCell = () => ( -
- - -
-); - -const StoryComponent = (props: TableProps & { showUpdates?: boolean }) => { - const [tableState, setTableState] = useState(props.data); - const [firstLoad, setFirstLoad] = useState(true); - - const onChangeHandler = (data: SampleSchema[]) => { - setTableState(data); - setFirstLoad(false); - }; - - return ( - <> -
- - columns={props.columns} - data={tableState} - title={props.title} - initialState={props.initialState} - footer={props.footer} - setSorting={props.setSorting} - draggableRows={props.draggableRows === true} - onChange={onChangeHandler} - subRowsKey={props.subRowsKey} - allowEditGroupsWithDnD={props.allowEditGroupsWithDnD} - /> -
- {props.showUpdates === true && !firstLoad && ( -
- Updated state -
    - {tableState?.map((item: SampleSchema, index: number) => { - const children = props.subRowsKey - ? (get(item, props.subRowsKey) || []).map((child: SampleSchema) => child.title) - : []; - return ( - // eslint-disable-next-line react/no-array-index-key -
  • {`${item.title} ${children.join(', ')}`}
  • - ); - })} -
-
- )} - - ); -}; - -const updatedData = [ - { title: 'Entity 2', created: 2, description: 'Short text' }, - { - title: 'Entity 3', - created: 3, - description: 'Morbi congue et justo vitae congue. Vivamus porttitor et leo vitae efficitur', - }, -]; - -const CheckboxesTableComponent = (args: TableProps) => { - const [selected1, setSelected1] = useState[]>([]); - const [selected2, setSelected2] = useState[]>([]); - const [table2Data, setTable2Data] = useState(args.data); - - return ( -
- - columns={args.columns} - data={args.data} - title="Table A" - enableSelection - onSelection={setSelected1} - draggableRows={args.draggableRows} - /> - -

Selected items for Table A: {selected1.length}

-

- Selections of Table A: {selected1.map(sel => `${sel.original.title}, `)} -

- -
- - - columns={args.columns} - data={table2Data} - title="Table B" - enableSelection - onSelection={setSelected2} - draggableRows={args.draggableRows} - /> - -

Selected items for Table B: {selected2.length}

-

- Selections of Table B: {selected2.map(sel => `${sel.original.title}, `)} -

- -
- - - -
-
- ); -}; - -const TitleCell = ({ row, getValue }: CellContext) => ( -
- - {getValue()} - - {row.getCanExpand() && ( - : } - onClick={() => row.toggleExpanded()} - color="indigo" - data-test-id="chevron-icon" - > - children - - )} -
-); - -const basicColumns = [ - columnHelper.accessor('title', { header: 'Title', id: 'title', cell: TitleCell }), - columnHelper.accessor('description', { header: 'Description' }), - columnHelper.accessor('created', { - header: 'Date added', - cell: CustomCell, - meta: { headerClassName: 'something' }, - }), -]; - -const withActionsColumns = [ - columnHelper.accessor('title', { - id: 'title', - header: 'Title', - meta: { headerClassName: 'w-2/4' }, - }), - columnHelper.accessor('created', { - id: 'created', - header: 'Date added', - meta: { headerClassName: 'w-1/4' }, - }), - columnHelper.accessor('description', { - id: 'description', - header: 'Description', - enableSorting: false, - meta: { headerClassName: 'w-1/4 bg-error-100 text-blue-600' }, - }), - columnHelper.display({ - id: 'action', - header: 'Actions', - cell: ActionsCell, - meta: { headerClassName: 'sr-only' }, - }), -]; - -export type { SampleSchema }; -export { StoryComponent, CheckboxesTableComponent, basicColumns, withActionsColumns }; diff --git a/package.json b/package.json index 853744afd4..3f785cdadb 100644 --- a/package.json +++ b/package.json @@ -201,8 +201,6 @@ "react-color": "^2.19.3", "react-datepicker": "7.3.0", "react-device-detect": "^2.2.3", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend-old": "yarn:react-dnd-html5-backend@^15.1.2", "react-dnd-old": "yarn:react-dnd@2.6.0", "react-dom": "^18.3.1", diff --git a/yarn.lock b/yarn.lock index 289b0b8cb7..0cc3fd5987 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15108,13 +15108,6 @@ react-device-detect@^2.2.3: dependencies: dnd-core "15.1.2" -react-dnd-html5-backend@^16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6" - integrity sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw== - dependencies: - dnd-core "^16.0.1" - "react-dnd-old@yarn:react-dnd@2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-2.6.0.tgz#7fa25676cf827d58a891293e3c1ab59da002545a" @@ -15134,7 +15127,7 @@ react-dnd-test-backend@15.1.1: dependencies: dnd-core "15.1.1" -react-dnd@*, react-dnd@^16.0.1: +react-dnd@*: version "16.0.1" resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-16.0.1.tgz#2442a3ec67892c60d40a1559eef45498ba26fa37" integrity sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q== From a9bc97efba121c281ab15f366fb6aa09cc04ddae Mon Sep 17 00:00:00 2001 From: Santiago Date: Fri, 30 Aug 2024 16:43:49 -0300 Subject: [PATCH 34/36] update translations --- contents/ui-translations/ar.csv | 1 - contents/ui-translations/en.csv | 1 - contents/ui-translations/es.csv | 1 - contents/ui-translations/fr.csv | 1 - contents/ui-translations/ko.csv | 1 - contents/ui-translations/my.csv | 1 - contents/ui-translations/ru.csv | 1 - contents/ui-translations/th.csv | 1 - contents/ui-translations/tr.csv | 1 - 9 files changed, 9 deletions(-) diff --git a/contents/ui-translations/ar.csv b/contents/ui-translations/ar.csv index be30d3627b..053b880990 100644 --- a/contents/ui-translations/ar.csv +++ b/contents/ui-translations/ar.csv @@ -265,7 +265,6 @@ documents.,مستندات Download,تحميل Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,اسحب الملف وأفلته في هذه النافذة للتحميل -Drag items here,اسحب العناصر هنا Drag properties here,اسحب الخصائص هنا Drag row,Drag row Drop your files here to upload or,اسحب ملفاتك هنا للتحميل أو diff --git a/contents/ui-translations/en.csv b/contents/ui-translations/en.csv index 0ace762af2..9429e8df86 100644 --- a/contents/ui-translations/en.csv +++ b/contents/ui-translations/en.csv @@ -268,7 +268,6 @@ documents.,documents. Download,Download Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Drag and drop file in this window to upload -Drag items here,Drag items here Drag properties here,Drag properties here Drag row,Drag row Drop your files here to upload or,Drop your files here to upload or diff --git a/contents/ui-translations/es.csv b/contents/ui-translations/es.csv index c7fcbd3c9d..3dcdc2221f 100644 --- a/contents/ui-translations/es.csv +++ b/contents/ui-translations/es.csv @@ -264,7 +264,6 @@ documents.,documentos. Download,Descargar Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Arrastrar y soltar el archivo en esta ventana para subir. -Drag items here,Arrastra aquí Drag properties here,Arrastra las propiedades aquí Drag row,Drag row Drop your files here to upload or,Suelta los archivos aquí para cargar o diff --git a/contents/ui-translations/fr.csv b/contents/ui-translations/fr.csv index 64c7e18394..449544d733 100644 --- a/contents/ui-translations/fr.csv +++ b/contents/ui-translations/fr.csv @@ -265,7 +265,6 @@ documents.,Documents. Download,Télécharger Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Faites glisser et déposez le fichier dans cette fenêtre pour le télécharger -Drag items here,Faire glisser les éléments ici Drag properties here,Faites glisser les propriétés ici Drag row,Drag row Drop your files here to upload or,Déposez vos fichiers ici pour les télécharger ou diff --git a/contents/ui-translations/ko.csv b/contents/ui-translations/ko.csv index fedda4e47b..b5c11dc68e 100644 --- a/contents/ui-translations/ko.csv +++ b/contents/ui-translations/ko.csv @@ -266,7 +266,6 @@ documents.,문서 Download,다운로드 Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,이 창에 파일을 드래그 앤 드롭해 업로드합니다. -Drag items here,여기에 아이템을 드래그합니다. Drag properties here,여기에 속성을 드래그합니다. Drag row,Drag row Drop your files here to upload or,여기에 파일을 드롭해 업로드합니다. diff --git a/contents/ui-translations/my.csv b/contents/ui-translations/my.csv index 6fde2e2853..c1163363dc 100644 --- a/contents/ui-translations/my.csv +++ b/contents/ui-translations/my.csv @@ -266,7 +266,6 @@ documents.,စာရွက်စာတမ်းများ။ Download,ဒေါင်းလုဒ်လုပ်ရန် Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,အပ်လုဒ်လုပ်ရန် ဖိုင်ကို ဤဝင်းဒိုးထဲသို့ ဆွဲချပါ -Drag items here,ဖိုင်များကို ဒီဘက် ဆွဲရွှေ့ပါ Drag properties here,ထူးခြားချက်များကို ဒီဘက် ဆွဲရွှေ့ပါ Drag row,Drag row Drop your files here to upload or,သင့်ဖိုင်များကို အပ်လုဒ်လုပ်ရန် ဒီနေရာတွင် ချပါ သို့မဟုတ် diff --git a/contents/ui-translations/ru.csv b/contents/ui-translations/ru.csv index 1e0e5a4653..3a7edf4e77 100644 --- a/contents/ui-translations/ru.csv +++ b/contents/ui-translations/ru.csv @@ -263,7 +263,6 @@ documents.,документы. Download,Скачать Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,"Перетащите и опустите файл в это окно, чтобы загрузить" -Drag items here,Перетащите элементы сюда Drag properties here,Перетащите свойства сюда Drag row,Drag row Drop your files here to upload or,"Опустите свои файлы сюда, чтобы загрузить или" diff --git a/contents/ui-translations/th.csv b/contents/ui-translations/th.csv index 18043171ea..08d1eb94f0 100644 --- a/contents/ui-translations/th.csv +++ b/contents/ui-translations/th.csv @@ -266,7 +266,6 @@ documents.,เอกสาร. Download,ดาวน์โหลด Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,นำไฟล์มาวางในหน้าต่างนี้เพื่ออัปโหลด -Drag items here,นำรายการมาวางที่นี่ Drag properties here,นำคุณสมบัติมาวางที่นี่ Drag row,Drag row Drop your files here to upload or,นำไฟล์มาวางที่นี่เพื่ออัปโหลด หรือ diff --git a/contents/ui-translations/tr.csv b/contents/ui-translations/tr.csv index 6ce3d9f807..bfbf044151 100644 --- a/contents/ui-translations/tr.csv +++ b/contents/ui-translations/tr.csv @@ -266,7 +266,6 @@ documents.,belgeler. Download,Özel filtre için belgeler Download a third-party authenticator app from your mobile store.,Download a third-party authenticator app from your mobile store. Drag and drop file in this window to upload,Yüklemek için dosyayı bu pencerede sürükleyip bırakın -Drag items here,Öğeleri buraya sürükleyin Drag properties here,Özellikleri buraya sürükleyin Drag row,Drag row Drop your files here to upload or,Dosyalarınızı yüklemek için buraya bırakın veya From 92777c6d319c0e6b1722d9a2c6906a5e788db18d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 15:26:15 -0500 Subject: [PATCH 35/36] Bump sharp from 0.33.2 to 0.33.5 (#7166) * Bump sharp from 0.33.2 to 0.33.5 Bumps [sharp](https://github.com/lovell/sharp) from 0.33.2 to 0.33.5. - [Release notes](https://github.com/lovell/sharp/releases) - [Changelog](https://github.com/lovell/sharp/blob/main/docs/changelog.md) - [Commits](https://github.com/lovell/sharp/compare/v0.33.2...v0.33.5) --- updated-dependencies: - dependency-name: sharp dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * removes no used dep --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mercy --- package.json | 2 - yarn.lock | 244 +++++++++++++++++++++++++-------------------------- 2 files changed, 122 insertions(+), 124 deletions(-) diff --git a/package.json b/package.json index 853744afd4..ce83dda131 100644 --- a/package.json +++ b/package.json @@ -323,7 +323,6 @@ "@types/react-tabs-redux": "^4.0.0", "@types/recharts": "^1.8.24", "@types/redux-mock-store": "^1.0.3", - "@types/sharp": "^0.29.2", "@types/socket.io": "^3.0.2", "@types/socket.io-client": "^1.4.33", "@types/superagent": "^8.1.6", @@ -384,7 +383,6 @@ "rtlcss-webpack-plugin": "4.0.7", "sass": "^1.77.8", "sass-loader": "16.0.1", - "sharp": "^0.33.2", "storybook": "^8.1.11", "stream-mock": "^2.0.5", "supertest": "7.0.0", diff --git a/yarn.lock b/yarn.lock index 289b0b8cb7..5309f40e62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1925,10 +1925,10 @@ ms "^2.1.3" secure-json-parse "^2.4.0" -"@emnapi/runtime@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-0.45.0.tgz#e754de04c683263f34fd0c7f32adfe718bbe4ddd" - integrity sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w== +"@emnapi/runtime@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.2.0.tgz#71d018546c3a91f3b51106530edbc056b9f2f2e3" + integrity sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ== dependencies: tslib "^2.4.0" @@ -2238,118 +2238,118 @@ version "0.2.4" resolved "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz" -"@img/sharp-darwin-arm64@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz#0a52a82c2169112794dac2c71bfba9e90f7c5bd1" - integrity sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w== +"@img/sharp-darwin-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.0.1" + "@img/sharp-libvips-darwin-arm64" "1.0.4" -"@img/sharp-darwin-x64@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz#982e26bb9d38a81f75915c4032539aed621d1c21" - integrity sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg== +"@img/sharp-darwin-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.0.1" + "@img/sharp-libvips-darwin-x64" "1.0.4" -"@img/sharp-libvips-darwin-arm64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz#81e83ffc2c497b3100e2f253766490f8fad479cd" - integrity sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw== +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== -"@img/sharp-libvips-darwin-x64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz#fc1fcd9d78a178819eefe2c1a1662067a83ab1d6" - integrity sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog== +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== -"@img/sharp-libvips-linux-arm64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz#26eb8c556a9b0db95f343fc444abc3effb67ebcf" - integrity sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA== +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== -"@img/sharp-libvips-linux-arm@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz#2a377b959ff7dd6528deee486c25461296a4fa8b" - integrity sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ== +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== -"@img/sharp-libvips-linux-s390x@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz#af28ac9ba929204467ecdf843330d791e9421e10" - integrity sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ== +"@img/sharp-libvips-linux-s390x@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" + integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== -"@img/sharp-libvips-linux-x64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz#4273d182aa51912e655e1214ea47983d7c1f7f8d" - integrity sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw== +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== -"@img/sharp-libvips-linuxmusl-arm64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz#d150c92151cea2e8d120ad168b9c358d09c77ce8" - integrity sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg== +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== -"@img/sharp-libvips-linuxmusl-x64@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz#e297c1a4252c670d93b0f9e51fca40a7a5b6acfd" - integrity sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw== +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== -"@img/sharp-linux-arm64@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz#af3409f801a9bee1d11d0c7e971dcd6180f80022" - integrity sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew== +"@img/sharp-linux-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.0.1" + "@img/sharp-libvips-linux-arm64" "1.0.4" -"@img/sharp-linux-arm@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz#181f7466e6ac074042a38bfb679eb82505e17083" - integrity sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA== +"@img/sharp-linux-arm@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.0.1" + "@img/sharp-libvips-linux-arm" "1.0.5" -"@img/sharp-linux-s390x@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz#9c171f49211f96fba84410b3e237b301286fa00f" - integrity sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA== +"@img/sharp-linux-s390x@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" + integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== optionalDependencies: - "@img/sharp-libvips-linux-s390x" "1.0.1" + "@img/sharp-libvips-linux-s390x" "1.0.4" -"@img/sharp-linux-x64@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz#b956dfc092adc58c2bf0fae2077e6f01a8b2d5d7" - integrity sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A== +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.0.1" + "@img/sharp-libvips-linux-x64" "1.0.4" -"@img/sharp-linuxmusl-arm64@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz#10e0ec5a79d1234c6a71df44c9f3b0bef0bc0f15" - integrity sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA== +"@img/sharp-linuxmusl-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64" "1.0.1" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" -"@img/sharp-linuxmusl-x64@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz#29e0030c24aa27c38201b1fc84e3d172899fcbe0" - integrity sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A== +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64" "1.0.1" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" -"@img/sharp-wasm32@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz#38d7c740a22de83a60ad1e6bcfce17462b0d4230" - integrity sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ== +"@img/sharp-wasm32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" + integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== dependencies: - "@emnapi/runtime" "^0.45.0" + "@emnapi/runtime" "^1.2.0" -"@img/sharp-win32-ia32@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz#09456314e223f68e5417c283b45c399635c16202" - integrity sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g== +"@img/sharp-win32-ia32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" + integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== -"@img/sharp-win32-x64@0.33.2": - version "0.33.2" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz#148e96dfd6e68747da41a311b9ee4559bb1b1471" - integrity sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg== +"@img/sharp-win32-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -8400,10 +8400,10 @@ detect-indent@^6.1.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== -detect-libc@^2.0.0, detect-libc@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" - integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== +detect-libc@^2.0.0, detect-libc@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== detect-newline@^3.0.0: version "3.1.0" @@ -16087,10 +16087,10 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: - version "7.6.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" - integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== +semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== send@0.18.0: version "0.18.0" @@ -16201,34 +16201,34 @@ shallow-compare@^1.2.1: resolved "https://registry.npmjs.org/shallow-compare/-/shallow-compare-1.2.2.tgz" integrity sha512-LUMFi+RppPlrHzbqmFnINTrazo0lPNwhcgzuAXVVcfy/mqPDrQmHAyz5bvV0gDAuRFrk804V0HpQ6u9sZ0tBeg== -sharp@^0.33.2: - version "0.33.2" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.2.tgz#fcd52f2c70effa8a02160b1bfd989a3de55f2dfb" - integrity sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ== +sharp@^0.33.5: + version "0.33.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" + integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== dependencies: color "^4.2.3" - detect-libc "^2.0.2" - semver "^7.5.4" + detect-libc "^2.0.3" + semver "^7.6.3" optionalDependencies: - "@img/sharp-darwin-arm64" "0.33.2" - "@img/sharp-darwin-x64" "0.33.2" - "@img/sharp-libvips-darwin-arm64" "1.0.1" - "@img/sharp-libvips-darwin-x64" "1.0.1" - "@img/sharp-libvips-linux-arm" "1.0.1" - "@img/sharp-libvips-linux-arm64" "1.0.1" - "@img/sharp-libvips-linux-s390x" "1.0.1" - "@img/sharp-libvips-linux-x64" "1.0.1" - "@img/sharp-libvips-linuxmusl-arm64" "1.0.1" - "@img/sharp-libvips-linuxmusl-x64" "1.0.1" - "@img/sharp-linux-arm" "0.33.2" - "@img/sharp-linux-arm64" "0.33.2" - "@img/sharp-linux-s390x" "0.33.2" - "@img/sharp-linux-x64" "0.33.2" - "@img/sharp-linuxmusl-arm64" "0.33.2" - "@img/sharp-linuxmusl-x64" "0.33.2" - "@img/sharp-wasm32" "0.33.2" - "@img/sharp-win32-ia32" "0.33.2" - "@img/sharp-win32-x64" "0.33.2" + "@img/sharp-darwin-arm64" "0.33.5" + "@img/sharp-darwin-x64" "0.33.5" + "@img/sharp-libvips-darwin-arm64" "1.0.4" + "@img/sharp-libvips-darwin-x64" "1.0.4" + "@img/sharp-libvips-linux-arm" "1.0.5" + "@img/sharp-libvips-linux-arm64" "1.0.4" + "@img/sharp-libvips-linux-s390x" "1.0.4" + "@img/sharp-libvips-linux-x64" "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@img/sharp-linux-arm" "0.33.5" + "@img/sharp-linux-arm64" "0.33.5" + "@img/sharp-linux-s390x" "0.33.5" + "@img/sharp-linux-x64" "0.33.5" + "@img/sharp-linuxmusl-arm64" "0.33.5" + "@img/sharp-linuxmusl-x64" "0.33.5" + "@img/sharp-wasm32" "0.33.5" + "@img/sharp-win32-ia32" "0.33.5" + "@img/sharp-win32-x64" "0.33.5" shebang-command@^2.0.0: version "2.0.0" From c3365e7f1c82d22eb0ebc6260357c5b3c343e808 Mon Sep 17 00:00:00 2001 From: Santiago <71732018+Zasa-san@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:32:56 -0300 Subject: [PATCH 36/36] Thesauri cy stabilization (#7171) * select other country to reload entity properly * comment for clarification --- cypress/e2e/settings/thesauri.cy.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cypress/e2e/settings/thesauri.cy.ts b/cypress/e2e/settings/thesauri.cy.ts index c03b505178..4fb1ca9132 100644 --- a/cypress/e2e/settings/thesauri.cy.ts +++ b/cypress/e2e/settings/thesauri.cy.ts @@ -206,6 +206,9 @@ describe('Thesauri configuration', () => { saveThesaurus(); cy.contains('a', 'Library').click(); cy.contains('.multiselectItem-name', 'País'); + //for the library sidepanel to reload by selecting another entity first so that 'País select' + //loads correctly. + cy.contains('.item-document', 'Bolivia').click(); cy.contains('.item-document', 'País select').click(); cy.contains('.metadata-name-select', 'Colors: Blue'); });