From 0f22d2165ff1e9aa5e771851679864382fb2347b Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 2 Dec 2025 17:11:35 -0300 Subject: [PATCH 1/3] refactor: get custom fields from `useCustomFieldsQuery` --- apps/meteor/client/lib/queryKeys.ts | 1 + .../contactInfo/hooks/useCustomFieldsQuery.ts | 8 ----- .../contactInfo/hooks/useValidCustomFields.ts | 5 +-- .../currentChats/CurrentChatsPage.tsx | 4 +-- .../currentChats/hooks/useAllCustomFields.tsx | 13 ------- .../customFields/CustomFieldsTable.tsx | 11 +++--- .../directory/chats/ChatInfo/ChatInfo.tsx | 34 +++---------------- .../chats/ChatsFiltersContextualBar.tsx | 7 ++-- .../hooks/useCustomFieldsMetadata.tsx | 14 +++----- .../utils/formatCustomFieldsMetadata.tsx | 4 +-- .../omnichannel/hooks/useCustomFieldsQuery.ts | 11 ++++++ 11 files changed, 36 insertions(+), 76 deletions(-) delete mode 100644 apps/meteor/client/views/omnichannel/contactInfo/hooks/useCustomFieldsQuery.ts delete mode 100644 apps/meteor/client/views/omnichannel/currentChats/hooks/useAllCustomFields.tsx create mode 100644 apps/meteor/client/views/omnichannel/hooks/useCustomFieldsQuery.ts diff --git a/apps/meteor/client/lib/queryKeys.ts b/apps/meteor/client/lib/queryKeys.ts index 904fc4b2f90e3..8ac2af48d0a58 100644 --- a/apps/meteor/client/lib/queryKeys.ts +++ b/apps/meteor/client/lib/queryKeys.ts @@ -50,6 +50,7 @@ export const omnichannelQueryKeys = { livechat: { appearance: () => [...omnichannelQueryKeys.all, 'livechat', 'appearance'] as const, customFields: () => [...omnichannelQueryKeys.all, 'livechat', 'custom-fields'] as const, + customFieldsMetadata: (scope: 'visitor' | 'room') => [...omnichannelQueryKeys.all, 'livechat', 'custom-fields', scope] as const, }, visitorInfo: (uid: string) => [...omnichannelQueryKeys.all, 'visitor-info', uid] as const, analytics: { diff --git a/apps/meteor/client/views/omnichannel/contactInfo/hooks/useCustomFieldsQuery.ts b/apps/meteor/client/views/omnichannel/contactInfo/hooks/useCustomFieldsQuery.ts deleted file mode 100644 index c714915166dcb..0000000000000 --- a/apps/meteor/client/views/omnichannel/contactInfo/hooks/useCustomFieldsQuery.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useEndpoint } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; - -// TODO: Unify this hook with all the other with the same proposal -export const useCustomFieldsQuery = () => { - const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); - return useQuery({ queryKey: ['/v1/livechat/custom-fields'], queryFn: async () => getCustomFields() }); -}; diff --git a/apps/meteor/client/views/omnichannel/contactInfo/hooks/useValidCustomFields.ts b/apps/meteor/client/views/omnichannel/contactInfo/hooks/useValidCustomFields.ts index c2ede88379438..70fbaca379c8b 100644 --- a/apps/meteor/client/views/omnichannel/contactInfo/hooks/useValidCustomFields.ts +++ b/apps/meteor/client/views/omnichannel/contactInfo/hooks/useValidCustomFields.ts @@ -1,9 +1,10 @@ +import type { ILivechatCustomField } from '@rocket.chat/core-typings'; import { usePermission } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; -import { useCustomFieldsQuery } from './useCustomFieldsQuery'; +import { useCustomFieldsQuery } from '../../hooks/useCustomFieldsQuery'; -const checkIsVisibleAndScopeVisitor = (key: string, customFields: Record[]) => { +const checkIsVisibleAndScopeVisitor = (key: string, customFields: ILivechatCustomField[]) => { const field = customFields?.find(({ _id }) => _id === key); return field?.visibility === 'visible' && field?.scope === 'visitor'; }; diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index b8108af6da0fc..2105051a79323 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -12,7 +12,6 @@ import { Trans, useTranslation } from 'react-i18next'; import CustomFieldsList from './CustomFieldsList'; import FilterByText from './FilterByText'; import RemoveChatButton from './RemoveChatButton'; -import { useAllCustomFields } from './hooks/useAllCustomFields'; import { useCurrentChats } from './hooks/useCurrentChats'; import GenericNoResults from '../../../components/GenericNoResults'; import { @@ -29,6 +28,7 @@ import { useSort } from '../../../components/GenericTable/hooks/useSort'; import { Page, PageHeader, PageContent } from '../../../components/Page'; import { links } from '../../../lib/links'; import RoomActivityIcon from '../components/RoomActivityIcon'; +import { useCustomFieldsQuery } from '../hooks/useCustomFieldsQuery'; import { useIsOverMacLimit } from '../hooks/useIsOverMacLimit'; import { useOmnichannelPriorities } from '../hooks/useOmnichannelPriorities'; import { PriorityIcon } from '../priorities/PriorityIcon'; @@ -138,7 +138,7 @@ const CurrentChatsPage = ({ id, onRowClick }: { id?: string; onRowClick: (_id: s const canRemoveClosedChats = usePermission('remove-closed-livechat-room'); const { enabled: isPriorityEnabled } = useOmnichannelPriorities(); - const { data: allCustomFields } = useAllCustomFields(); + const { data: allCustomFields } = useCustomFieldsQuery(); const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); diff --git a/apps/meteor/client/views/omnichannel/currentChats/hooks/useAllCustomFields.tsx b/apps/meteor/client/views/omnichannel/currentChats/hooks/useAllCustomFields.tsx deleted file mode 100644 index 2b66d8822b656..0000000000000 --- a/apps/meteor/client/views/omnichannel/currentChats/hooks/useAllCustomFields.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { OperationResult } from '@rocket.chat/rest-typings'; -import { useEndpoint } from '@rocket.chat/ui-contexts'; -import type { UseQueryResult } from '@tanstack/react-query'; -import { useQuery } from '@tanstack/react-query'; - -export const useAllCustomFields = (): UseQueryResult> => { - const allCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); - - return useQuery({ - queryKey: ['livechat/custom-fields'], - queryFn: async () => allCustomFields(), - }); -}; diff --git a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx index 5b3f9bb64bf0a..8e48807e66250 100644 --- a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx +++ b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsTable.tsx @@ -1,7 +1,7 @@ import { IconButton, Pagination } from '@rocket.chat/fuselage'; import { useDebouncedValue, useEffectEvent } from '@rocket.chat/fuselage-hooks'; -import { useTranslation, useEndpoint, useRouter } from '@rocket.chat/ui-contexts'; -import { useQuery, hashKey } from '@tanstack/react-query'; +import { useTranslation, useRouter } from '@rocket.chat/ui-contexts'; +import { hashKey } from '@tanstack/react-query'; import { useMemo, useState } from 'react'; import { useRemoveCustomField } from './useRemoveCustomField'; @@ -19,6 +19,7 @@ import { import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; import { links } from '../../../lib/links'; +import { useCustomFieldsQuery } from '../hooks/useCustomFieldsQuery'; const CustomFieldsTable = () => { const t = useTranslation(); @@ -46,11 +47,7 @@ const CustomFieldsTable = () => { 500, ); - const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); - const { data, isSuccess, isLoading } = useQuery({ - queryKey: ['livechat-customFields', query], - queryFn: async () => getCustomFields(query), - }); + const { data, isSuccess, isLoading } = useCustomFieldsQuery(); const [defaultQuery] = useState(hashKey([query])); const queryHasChanged = defaultQuery !== hashKey([query]); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx index 3b126d2d29ae4..2df005f213bb1 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/ChatInfo/ChatInfo.tsx @@ -3,16 +3,7 @@ import { Box, Margins, Tag, Button, ButtonGroup } from '@rocket.chat/fuselage'; import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; import { ContextualbarScrollableContent, ContextualbarFooter } from '@rocket.chat/ui-client'; import type { IRouterPaths } from '@rocket.chat/ui-contexts'; -import { - useToastMessageDispatch, - useRoute, - useUserSubscription, - useTranslation, - usePermission, - useEndpoint, - useUserId, -} from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useToastMessageDispatch, useRoute, useUserSubscription, useTranslation, usePermission, useUserId } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import { useMemo } from 'react'; @@ -22,8 +13,8 @@ import { InfoPanelField, InfoPanelLabel, InfoPanelText } from '../../../../../co import MarkdownText from '../../../../../components/MarkdownText'; import { useFormatDateAndTime } from '../../../../../hooks/useFormatDateAndTime'; import { useFormatDuration } from '../../../../../hooks/useFormatDuration'; -import { omnichannelQueryKeys } from '../../../../../lib/queryKeys'; import CustomField from '../../../components/CustomField'; +import { useValidCustomFields } from '../../../contactInfo/hooks/useValidCustomFields'; import { AgentField, SlaField, ContactField, SourceField } from '../../components'; import PriorityField from '../../components/PriorityField'; import { useOmnichannelRoomInfo } from '../../hooks/useOmnichannelRoomInfo'; @@ -40,14 +31,6 @@ function ChatInfo({ id, route }: ChatInfoProps) { const dispatchToastMessage = useToastMessageDispatch(); const formatDateAndTime = useFormatDateAndTime(); - const getLivechatCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); - const { data: customFields = [] } = useQuery({ - queryKey: omnichannelQueryKeys.livechat.customFields(), - queryFn: async () => { - const { customFields } = await getLivechatCustomFields(); - return customFields; - }, - }); const formatDuration = useFormatDuration(); const { data: room } = useOmnichannelRoomInfo(id); // FIXME: `room` is serialized, but we need to deserialize it @@ -72,7 +55,6 @@ function ChatInfo({ id, route }: ChatInfoProps) { } = room ?? {}; const routePath = useRoute(route || 'omnichannel-directory'); - const canViewCustomFields = usePermission('view-livechat-room-customfields'); const subscription = useUserSubscription(id); const hasGlobalEditRoomPermission = usePermission('save-others-livechat-room-info'); const hasLocalEditRoomPermission = servedBy?._id === useUserId(); @@ -81,10 +63,7 @@ function ChatInfo({ id, route }: ChatInfoProps) { const queueTime = useMemo(() => formatQueuedAt(room), [room]); - const checkIsVisibleAndScopeRoom = (key: string) => { - const field = customFields.find(({ _id }) => _id === key); - return field?.visibility === 'visible' && field?.scope === 'room'; - }; + const customFieldEntries = useValidCustomFields(livechatData); const onEditClick = useEffectEvent(() => { const hasEditAccess = !!subscription || hasLocalEditRoomPermission || hasGlobalEditRoomPermission; @@ -107,10 +86,6 @@ function ChatInfo({ id, route }: ChatInfoProps) { ); }); - const customFieldEntries: [string, any][] = Object.entries(livechatData || {}).filter( - ([key]) => checkIsVisibleAndScopeRoom(key) && livechatData[key], - ); - return ( <> @@ -186,7 +161,8 @@ function ChatInfo({ id, route }: ChatInfoProps) { {moment(responseBy.lastMessageTs).fromNow(true)} )} - {canViewCustomFields && customFieldEntries.map(([key, value]) => )} + {customFieldEntries?.length > 0 && + customFieldEntries.map(([key, value]) => )} {slaId && } {priorityId && } diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatsFiltersContextualBar.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatsFiltersContextualBar.tsx index f3da774979314..45419795ae30c 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/ChatsFiltersContextualBar.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/ChatsFiltersContextualBar.tsx @@ -8,8 +8,7 @@ import { ContextualbarFooter, ContextualbarDialog, } from '@rocket.chat/ui-client'; -import { useEndpoint, usePermission } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { usePermission } from '@rocket.chat/ui-contexts'; import { format } from 'date-fns'; import { useId } from 'react'; import { Controller, useForm } from 'react-hook-form'; @@ -20,6 +19,7 @@ import { CurrentChatTags } from '../../additionalForms'; import AutoCompleteUnits from '../../additionalForms/AutoCompleteUnits'; import AutoCompleteDepartmentMultiple from '../../components/AutoCompleteDepartmentMultiple'; import AutoCompleteMultipleAgent from '../../components/AutoCompleteMultipleAgent'; +import { useCustomFieldsQuery } from '../../hooks/useCustomFieldsQuery'; import type { ChatsFiltersQuery } from '../contexts/ChatsContext'; import { useChatsContext } from '../contexts/ChatsContext'; @@ -33,8 +33,7 @@ const ChatsFiltersContextualBar = ({ onClose }: ChatsFiltersContextualBarProps) const canViewCustomFields = usePermission('view-livechat-room-customfields'); const { data: isEnterprise = false } = useHasLicenseModule('livechat-enterprise'); - const allCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); - const { data } = useQuery({ queryKey: ['livechat/custom-fields'], queryFn: async () => allCustomFields() }); + const { data } = useCustomFieldsQuery(); const contactCustomFields = data?.customFields.filter((customField) => customField.scope !== 'visitor'); const { filtersQuery, setFiltersQuery, resetFiltersQuery, hasAppliedFilters } = useChatsContext(); diff --git a/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx b/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx index 85437af895a03..cc14fb1d84531 100644 --- a/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx +++ b/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx @@ -1,6 +1,7 @@ -import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; +import { omnichannelQueryKeys } from '../../../../lib/queryKeys'; +import { useCustomFieldsQuery } from '../../hooks/useCustomFieldsQuery'; import { formatCustomFieldsMetadata } from '../utils/formatCustomFieldsMetadata'; type UseCustomFieldsMetadataOptions = { @@ -9,15 +10,10 @@ type UseCustomFieldsMetadataOptions = { }; export const useCustomFieldsMetadata = ({ enabled = true, scope }: UseCustomFieldsMetadataOptions) => { - const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); + const { data } = useCustomFieldsQuery(); return useQuery({ - queryKey: ['/v1/livechat/custom-fields', scope], - - queryFn: async () => { - const { customFields } = (await getCustomFields()) ?? {}; - return formatCustomFieldsMetadata(customFields, scope); - }, - + queryKey: omnichannelQueryKeys.livechat.customFieldsMetadata(scope), + queryFn: async () => formatCustomFieldsMetadata(data?.customFields, scope), enabled, }); }; diff --git a/apps/meteor/client/views/omnichannel/directory/utils/formatCustomFieldsMetadata.tsx b/apps/meteor/client/views/omnichannel/directory/utils/formatCustomFieldsMetadata.tsx index b5648d67199f2..639cb63095b7a 100644 --- a/apps/meteor/client/views/omnichannel/directory/utils/formatCustomFieldsMetadata.tsx +++ b/apps/meteor/client/views/omnichannel/directory/utils/formatCustomFieldsMetadata.tsx @@ -1,7 +1,7 @@ -import type { ILivechatCustomField, Serialized, CustomFieldMetadata } from '@rocket.chat/core-typings'; +import type { ILivechatCustomField, CustomFieldMetadata } from '@rocket.chat/core-typings'; export const formatCustomFieldsMetadata = ( - customFields: Serialized[], + customFields: ILivechatCustomField[] | undefined, scope: 'visitor' | 'room', ): CustomFieldMetadata[] => { if (!customFields) { diff --git a/apps/meteor/client/views/omnichannel/hooks/useCustomFieldsQuery.ts b/apps/meteor/client/views/omnichannel/hooks/useCustomFieldsQuery.ts new file mode 100644 index 0000000000000..6ef0449e0a89c --- /dev/null +++ b/apps/meteor/client/views/omnichannel/hooks/useCustomFieldsQuery.ts @@ -0,0 +1,11 @@ +import type { OperationResult } from '@rocket.chat/rest-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import type { UseQueryResult } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; + +import { omnichannelQueryKeys } from '../../../lib/queryKeys'; + +export const useCustomFieldsQuery = (): UseQueryResult> => { + const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields'); + return useQuery({ queryKey: omnichannelQueryKeys.livechat.customFields(), queryFn: async () => getCustomFields() }); +}; From 2ac5ec58eb5067d02dd8430339f310bff2e93f7b Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 2 Dec 2025 19:10:19 -0300 Subject: [PATCH 2/3] chore: queryKey invalidation --- .../views/omnichannel/customFields/EditCustomFields.tsx | 3 ++- .../views/omnichannel/customFields/useRemoveCustomField.tsx | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/omnichannel/customFields/EditCustomFields.tsx b/apps/meteor/client/views/omnichannel/customFields/EditCustomFields.tsx index bfe82257ad713..80833cd8ff050 100644 --- a/apps/meteor/client/views/omnichannel/customFields/EditCustomFields.tsx +++ b/apps/meteor/client/views/omnichannel/customFields/EditCustomFields.tsx @@ -28,6 +28,7 @@ import { FormProvider, useForm, Controller } from 'react-hook-form'; import { CustomFieldsAdditionalForm } from '../additionalForms'; import { useRemoveCustomField } from './useRemoveCustomField'; +import { omnichannelQueryKeys } from '../../../lib/queryKeys'; export type EditCustomFieldsFormData = { field: string; @@ -86,7 +87,7 @@ const EditCustomFields = ({ customFieldData, onClose }: { customFieldData?: Seri dispatchToastMessage({ type: 'success', message: t('Saved') }); queryClient.invalidateQueries({ - queryKey: ['livechat-customFields'], + queryKey: omnichannelQueryKeys.livechat.customFields(), }); onClose(); } catch (error) { diff --git a/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx b/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx index 80164cdab9540..d03059d91e8ae 100644 --- a/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx +++ b/apps/meteor/client/views/omnichannel/customFields/useRemoveCustomField.tsx @@ -4,6 +4,8 @@ import { useSetModal, useToastMessageDispatch, useEndpoint } from '@rocket.chat/ import { useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; +import { omnichannelQueryKeys } from '../../../lib/queryKeys'; + export const useRemoveCustomField = () => { const { t } = useTranslation(); const setModal = useSetModal(); @@ -17,7 +19,7 @@ export const useRemoveCustomField = () => { await removeCustomField({ customFieldId: id }); dispatchToastMessage({ type: 'success', message: t('Custom_Field_Removed') }); queryClient.invalidateQueries({ - queryKey: ['livechat-customFields'], + queryKey: omnichannelQueryKeys.livechat.customFields(), }); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); From 3917f1bffc68ff9d3e3e358a7b26ea3e987798a8 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 2 Dec 2025 21:07:45 -0300 Subject: [PATCH 3/3] fix: review --- .../omnichannel/directory/hooks/useCustomFieldsMetadata.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx b/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx index cc14fb1d84531..feaba3944a7c8 100644 --- a/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx +++ b/apps/meteor/client/views/omnichannel/directory/hooks/useCustomFieldsMetadata.tsx @@ -10,10 +10,10 @@ type UseCustomFieldsMetadataOptions = { }; export const useCustomFieldsMetadata = ({ enabled = true, scope }: UseCustomFieldsMetadataOptions) => { - const { data } = useCustomFieldsQuery(); + const { data, isSuccess } = useCustomFieldsQuery(); return useQuery({ queryKey: omnichannelQueryKeys.livechat.customFieldsMetadata(scope), queryFn: async () => formatCustomFieldsMetadata(data?.customFields, scope), - enabled, + enabled: enabled && isSuccess, }); };