snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import type { IOmnichannelRoom } from '@rocket.chat/core-typings';
|
||||
import { Box, Dropdown, Option } from '@rocket.chat/fuselage';
|
||||
import type { Keys as IconName } from '@rocket.chat/icons';
|
||||
import { HeaderToolbarAction } from '@rocket.chat/ui-client';
|
||||
import { memo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useDropdownVisibility } from './hooks/useDropdownVisibility';
|
||||
import type { QuickActionsActionOptions } from '../../../lib/quickActions';
|
||||
|
||||
export type QuickActionOptionsProps = {
|
||||
options: QuickActionsActionOptions;
|
||||
action: (id: string) => void;
|
||||
room: IOmnichannelRoom;
|
||||
icon: IconName;
|
||||
};
|
||||
|
||||
const QuickActionOptions = ({ options, room, action, icon, ...props }: QuickActionOptionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const reference = useRef<HTMLButtonElement>(null);
|
||||
const target = useRef(null);
|
||||
const { isVisible, toggle } = useDropdownVisibility({ reference, target });
|
||||
|
||||
const handleClick = (id: string) => (): void => {
|
||||
toggle();
|
||||
action(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderToolbarAction ref={reference} icon={icon} onClick={() => toggle()} secondary={isVisible} {...props} />
|
||||
{isVisible && (
|
||||
<Dropdown reference={reference} ref={target}>
|
||||
{options.map(({ id, label, validate }) => {
|
||||
const { value: valid = true, tooltip } = validate?.(room) || {};
|
||||
return (
|
||||
<Option key={id} onClick={handleClick(id)} disabled={!valid} title={!valid && tooltip ? t(tooltip) : undefined}>
|
||||
<Box fontScale='p2m' minWidth='180px'>
|
||||
{t(label)}
|
||||
</Box>
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</Dropdown>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(QuickActionOptions);
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Box } from '@rocket.chat/fuselage';
|
||||
import { HeaderToolbar, HeaderToolbarAction, HeaderToolbarDivider } from '@rocket.chat/ui-client';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import QuickActionOptions from './QuickActionOptions';
|
||||
import { useQuickActions } from './hooks/useQuickActions';
|
||||
import { useOmnichannelRoom } from '../../../contexts/RoomContext';
|
||||
|
||||
export type QuickActionsProps = {
|
||||
className?: ComponentProps<typeof Box>['className'];
|
||||
};
|
||||
|
||||
const QuickActions = ({ className }: QuickActionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const room = useOmnichannelRoom();
|
||||
const { quickActions, actionDefault } = useQuickActions();
|
||||
|
||||
return (
|
||||
<HeaderToolbar aria-label={t('Omnichannel_quick_actions')}>
|
||||
{quickActions.map(({ id, color, icon, title, action = actionDefault, options }, index) => {
|
||||
const props = {
|
||||
id,
|
||||
icon,
|
||||
color,
|
||||
title: t(title),
|
||||
className,
|
||||
index,
|
||||
primary: false,
|
||||
room,
|
||||
};
|
||||
|
||||
if (options) {
|
||||
return <QuickActionOptions options={options} key={id} action={action} {...props} />;
|
||||
}
|
||||
|
||||
return <HeaderToolbarAction key={id} onClick={() => action(id)} {...props} />;
|
||||
})}
|
||||
{quickActions.length > 0 && <HeaderToolbarDivider />}
|
||||
</HeaderToolbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(QuickActions);
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useToggle, useOutsideClick } from '@rocket.chat/fuselage-hooks';
|
||||
import type { RefObject } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* useDropdownVisibility
|
||||
* is used to control the visibility of a dropdown
|
||||
* also checks if the user clicked outside the dropdown, but ignores if the click was on the anchor
|
||||
* @param {Object} props
|
||||
* @param {Object} props.reference - The reference where the dropdown will be attached to
|
||||
* @param {Object} props.target - The target, the dropdown itself
|
||||
* @returns {Object}
|
||||
* @returns {Boolean} isVisible - The visibility of the dropdown
|
||||
* @returns {Function} toggle - The function to toggle the dropdown
|
||||
*/
|
||||
|
||||
export const useDropdownVisibility = <T extends HTMLElement>({
|
||||
reference,
|
||||
target,
|
||||
}: {
|
||||
reference: RefObject<T | null>;
|
||||
target: RefObject<T | null>;
|
||||
}): {
|
||||
isVisible: boolean;
|
||||
toggle: (state?: boolean) => void;
|
||||
} => {
|
||||
const [isVisible, toggle] = useToggle(false);
|
||||
|
||||
useOutsideClick(
|
||||
[target, reference],
|
||||
useCallback(() => toggle(false), [toggle]),
|
||||
);
|
||||
|
||||
return {
|
||||
isVisible,
|
||||
toggle,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
import { useEndpoint } from '@rocket.chat/ui-contexts';
|
||||
import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { roomsQueryKeys, subscriptionsQueryKeys } from '../../../../../../lib/queryKeys';
|
||||
|
||||
export const usePutChatOnHoldMutation = (
|
||||
options?: Omit<UseMutationOptions<void, Error, IRoom['_id']>, 'mutationFn'>,
|
||||
): UseMutationResult<void, Error, IRoom['_id']> => {
|
||||
const putChatOnHold = useEndpoint('POST', '/v1/livechat/room.onHold');
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (rid) => {
|
||||
await putChatOnHold({ roomId: rid });
|
||||
},
|
||||
...options,
|
||||
onSuccess: async (data, rid, context) => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['current-chats'] });
|
||||
await queryClient.invalidateQueries({ queryKey: roomsQueryKeys.room(rid) });
|
||||
await queryClient.invalidateQueries({ queryKey: subscriptionsQueryKeys.subscription(rid) });
|
||||
return options?.onSuccess?.(data, rid, context);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,322 @@
|
||||
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
|
||||
import {
|
||||
useSetModal,
|
||||
useToastMessageDispatch,
|
||||
useUserId,
|
||||
useSetting,
|
||||
usePermission,
|
||||
useRole,
|
||||
useEndpoint,
|
||||
useTranslation,
|
||||
useRouter,
|
||||
} from '@rocket.chat/ui-contexts';
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
|
||||
import { usePutChatOnHoldMutation } from './usePutChatOnHoldMutation';
|
||||
import { useReturnChatToQueueMutation } from './useReturnChatToQueueMutation';
|
||||
import { useHasLicenseModule } from '../../../../../../hooks/useHasLicenseModule';
|
||||
import { useLivechatInquiryStore } from '../../../../../../hooks/useLivechatInquiryStore';
|
||||
import { LegacyRoomManager } from '../../../../../../lib/LegacyRoomManager';
|
||||
import { quickActionHooks } from '../../../../../../ui';
|
||||
import { useIsRoomOverMacLimit } from '../../../../../omnichannel/hooks/useIsRoomOverMacLimit';
|
||||
import { useOmnichannelRouteConfig } from '../../../../../omnichannel/hooks/useOmnichannelRouteConfig';
|
||||
import CloseChatModal from '../../../../../omnichannel/modals/CloseChatModal';
|
||||
import CloseChatModalData from '../../../../../omnichannel/modals/CloseChatModalData';
|
||||
import ForwardChatModal from '../../../../../omnichannel/modals/ForwardChatModal';
|
||||
import PlaceChatOnHoldModal from '../../../../../omnichannel/modals/PlaceChatOnHoldModal';
|
||||
import ReturnChatQueueModal from '../../../../../omnichannel/modals/ReturnChatQueueModal';
|
||||
import TranscriptModal from '../../../../../omnichannel/modals/TranscriptModal';
|
||||
import { useOmnichannelRoom } from '../../../../contexts/RoomContext';
|
||||
import type { QuickActionsActionConfig } from '../../../../lib/quickActions';
|
||||
import { QuickActionsEnum } from '../../../../lib/quickActions';
|
||||
|
||||
export const useQuickActions = (): {
|
||||
quickActions: QuickActionsActionConfig[];
|
||||
actionDefault: (actionId: string) => void;
|
||||
} => {
|
||||
const room = useOmnichannelRoom();
|
||||
const setModal = useSetModal();
|
||||
const router = useRouter();
|
||||
|
||||
const t = useTranslation();
|
||||
const dispatchToastMessage = useToastMessageDispatch();
|
||||
|
||||
const [onHoldModalActive, setOnHoldModalActive] = useState(false);
|
||||
|
||||
const visitorRoomId = room.v._id;
|
||||
const rid = room._id;
|
||||
const uid = useUserId();
|
||||
const roomLastMessage = room.lastMessage;
|
||||
|
||||
const getVisitorInfo = useEndpoint('GET', '/v1/livechat/visitors.info');
|
||||
|
||||
const getVisitorEmail = useStableCallback(async () => {
|
||||
if (!visitorRoomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
visitor: { visitorEmails },
|
||||
} = await getVisitorInfo({ visitorId: visitorRoomId });
|
||||
|
||||
if (visitorEmails?.length && visitorEmails[0].address) {
|
||||
return visitorEmails[0].address;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (onHoldModalActive && roomLastMessage?.token) {
|
||||
setModal(null);
|
||||
}
|
||||
}, [roomLastMessage, onHoldModalActive, setModal]);
|
||||
|
||||
const closeModal = useCallback(() => setModal(null), [setModal]);
|
||||
|
||||
const requestTranscript = useEndpoint('POST', '/v1/livechat/transcript/:rid', { rid });
|
||||
|
||||
const handleRequestTranscript = useCallback(
|
||||
async (email: string, subject: string) => {
|
||||
try {
|
||||
await requestTranscript({ email, subject });
|
||||
closeModal();
|
||||
dispatchToastMessage({
|
||||
type: 'success',
|
||||
message: t('Livechat_email_transcript_has_been_requested'),
|
||||
});
|
||||
} catch (error) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
}
|
||||
},
|
||||
[closeModal, dispatchToastMessage, requestTranscript, t],
|
||||
);
|
||||
|
||||
const sendTranscriptPDF = useEndpoint('POST', '/v1/omnichannel/:rid/request-transcript', { rid });
|
||||
|
||||
const handleSendTranscriptPDF = useCallback(async () => {
|
||||
try {
|
||||
await sendTranscriptPDF();
|
||||
dispatchToastMessage({
|
||||
type: 'success',
|
||||
message: t('Livechat_transcript_has_been_requested'),
|
||||
});
|
||||
} catch (error) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
}
|
||||
}, [dispatchToastMessage, sendTranscriptPDF, t]);
|
||||
|
||||
const sendTranscript = useEndpoint('POST', '/v1/livechat/transcript');
|
||||
|
||||
const handleSendTranscript = useCallback(
|
||||
async (email: string, subject: string, token: string) => {
|
||||
try {
|
||||
await sendTranscript({ token, rid, email, subject });
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
}
|
||||
},
|
||||
[closeModal, dispatchToastMessage, rid, sendTranscript],
|
||||
);
|
||||
|
||||
const discardTranscript = useEndpoint('DELETE', '/v1/livechat/transcript/:rid', { rid });
|
||||
|
||||
const handleDiscardTranscript = useCallback(async () => {
|
||||
try {
|
||||
await discardTranscript();
|
||||
dispatchToastMessage({
|
||||
type: 'success',
|
||||
message: t('Livechat_transcript_request_has_been_canceled'),
|
||||
});
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
}
|
||||
}, [closeModal, discardTranscript, dispatchToastMessage, t]);
|
||||
|
||||
const closeChat = useEndpoint('POST', '/v1/livechat/room.closeByUser');
|
||||
|
||||
const discardForRoom = useLivechatInquiryStore((state) => state.discardForRoom);
|
||||
|
||||
const handleClose = useCallback(
|
||||
async (
|
||||
comment?: string,
|
||||
tags?: string[],
|
||||
preferences?: { omnichannelTranscriptPDF: boolean; omnichannelTranscriptEmail: boolean },
|
||||
requestData?: { email: string; subject: string },
|
||||
) => {
|
||||
try {
|
||||
await closeChat({
|
||||
rid,
|
||||
...(comment && { comment }),
|
||||
...(tags && { tags }),
|
||||
...(preferences?.omnichannelTranscriptPDF && { generateTranscriptPdf: true }),
|
||||
...(preferences?.omnichannelTranscriptEmail && requestData
|
||||
? {
|
||||
transcriptEmail: {
|
||||
sendToVisitor: preferences?.omnichannelTranscriptEmail,
|
||||
requestData,
|
||||
},
|
||||
}
|
||||
: { transcriptEmail: { sendToVisitor: false } }),
|
||||
});
|
||||
discardForRoom(rid);
|
||||
closeModal();
|
||||
dispatchToastMessage({ type: 'success', message: t('Chat_closed_successfully') });
|
||||
} catch (error) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
}
|
||||
},
|
||||
[closeChat, closeModal, dispatchToastMessage, rid, t, discardForRoom],
|
||||
);
|
||||
|
||||
const returnChatToQueueMutation = useReturnChatToQueueMutation({
|
||||
onSuccess: () => {
|
||||
LegacyRoomManager.close(room.t + rid);
|
||||
router.navigate('/home');
|
||||
},
|
||||
onError: (error) => {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
},
|
||||
onSettled: () => {
|
||||
closeModal();
|
||||
},
|
||||
});
|
||||
|
||||
const putChatOnHoldMutation = usePutChatOnHoldMutation({
|
||||
onSuccess: () => {
|
||||
dispatchToastMessage({ type: 'success', message: t('Chat_On_Hold_Successfully') });
|
||||
},
|
||||
onError: (error) => {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
},
|
||||
onSettled: () => {
|
||||
closeModal();
|
||||
},
|
||||
});
|
||||
|
||||
const handleAction = useStableCallback(async (id: string) => {
|
||||
switch (id) {
|
||||
case QuickActionsEnum.MoveQueue:
|
||||
setModal(
|
||||
<ReturnChatQueueModal
|
||||
onMoveChat={(): void => returnChatToQueueMutation.mutate(rid)}
|
||||
onCancel={(): void => {
|
||||
closeModal();
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
break;
|
||||
case QuickActionsEnum.TranscriptPDF:
|
||||
handleSendTranscriptPDF();
|
||||
break;
|
||||
case QuickActionsEnum.TranscriptEmail:
|
||||
const visitorEmail = await getVisitorEmail();
|
||||
|
||||
if (!visitorEmail) {
|
||||
dispatchToastMessage({ type: 'error', message: t('Customer_without_registered_email') });
|
||||
break;
|
||||
}
|
||||
|
||||
setModal(
|
||||
<TranscriptModal
|
||||
room={room}
|
||||
email={visitorEmail}
|
||||
onRequest={handleRequestTranscript}
|
||||
onSend={handleSendTranscript}
|
||||
onDiscard={handleDiscardTranscript}
|
||||
onCancel={closeModal}
|
||||
/>,
|
||||
);
|
||||
break;
|
||||
case QuickActionsEnum.ChatForward:
|
||||
setModal(<ForwardChatModal room={room} onCancel={closeModal} />);
|
||||
break;
|
||||
case QuickActionsEnum.CloseChat:
|
||||
const email = await getVisitorEmail();
|
||||
setModal(
|
||||
room.departmentId ? (
|
||||
<CloseChatModalData visitorEmail={email} departmentId={room.departmentId} onConfirm={handleClose} onCancel={closeModal} />
|
||||
) : (
|
||||
<CloseChatModal visitorEmail={email} onConfirm={handleClose} onCancel={closeModal} />
|
||||
),
|
||||
);
|
||||
break;
|
||||
case QuickActionsEnum.OnHoldChat:
|
||||
setModal(
|
||||
<PlaceChatOnHoldModal
|
||||
onOnHoldChat={(): void => putChatOnHoldMutation.mutate(rid)}
|
||||
onCancel={(): void => {
|
||||
closeModal();
|
||||
setOnHoldModalActive(false);
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
setOnHoldModalActive(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const omnichannelRouteConfig = useOmnichannelRouteConfig();
|
||||
|
||||
const manualOnHoldAllowed = useSetting('Livechat_allow_manual_on_hold');
|
||||
|
||||
const hasManagerRole = useRole('livechat-manager');
|
||||
const hasMonitorRole = useRole('livechat-monitor');
|
||||
|
||||
const roomOpen = room?.open && (room.u?._id === uid || hasManagerRole || hasMonitorRole) && room?.lastMessage?.t !== 'livechat-close';
|
||||
const canMoveQueue = !!omnichannelRouteConfig?.returnQueue && room?.u !== undefined;
|
||||
const canForwardGuest = usePermission('transfer-livechat-guest');
|
||||
const canSendTranscriptEmail = usePermission('send-omnichannel-chat-transcript');
|
||||
const { data: hasLicense = false } = useHasLicenseModule('livechat-enterprise');
|
||||
const canSendTranscriptPDF = usePermission('request-pdf-transcript');
|
||||
const canCloseRoom = usePermission('close-livechat-room');
|
||||
const canCloseOthersRoom = usePermission('close-others-livechat-room');
|
||||
const restrictedOnHold = useSetting('Livechat_allow_manual_on_hold_upon_agent_engagement_only');
|
||||
const canRoomBePlacedOnHold = !room.onHold && room.u;
|
||||
const canAgentPlaceOnHold = !room.lastMessage?.token;
|
||||
const canPlaceChatOnHold = Boolean(manualOnHoldAllowed && canRoomBePlacedOnHold && (!restrictedOnHold || canAgentPlaceOnHold));
|
||||
const isRoomOverMacLimit = useIsRoomOverMacLimit(room);
|
||||
|
||||
const hasPermissionButtons = (id: string): boolean => {
|
||||
switch (id) {
|
||||
case QuickActionsEnum.MoveQueue:
|
||||
return !isRoomOverMacLimit && !!roomOpen && canMoveQueue;
|
||||
case QuickActionsEnum.ChatForward:
|
||||
return !isRoomOverMacLimit && !!roomOpen && canForwardGuest;
|
||||
case QuickActionsEnum.Transcript:
|
||||
return !isRoomOverMacLimit && (canSendTranscriptEmail || (hasLicense && canSendTranscriptPDF));
|
||||
case QuickActionsEnum.TranscriptEmail:
|
||||
return !isRoomOverMacLimit && canSendTranscriptEmail;
|
||||
case QuickActionsEnum.TranscriptPDF:
|
||||
return hasLicense && !isRoomOverMacLimit && canSendTranscriptPDF;
|
||||
case QuickActionsEnum.CloseChat:
|
||||
return !!roomOpen && (canCloseRoom || canCloseOthersRoom);
|
||||
case QuickActionsEnum.OnHoldChat:
|
||||
return !!roomOpen && canPlaceChatOnHold;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const quickActions = quickActionHooks
|
||||
.map((quickActionHook) => quickActionHook())
|
||||
.filter((quickAction): quickAction is QuickActionsActionConfig => !!quickAction)
|
||||
.filter((action) => {
|
||||
const { options, id } = action;
|
||||
if (options) {
|
||||
action.options = options.filter(({ id }) => hasPermissionButtons(id));
|
||||
}
|
||||
|
||||
return hasPermissionButtons(id);
|
||||
})
|
||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
||||
|
||||
const actionDefault = useStableCallback((actionId: string) => {
|
||||
handleAction(actionId);
|
||||
});
|
||||
|
||||
return { quickActions, actionDefault };
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
import { useEndpoint } from '@rocket.chat/ui-contexts';
|
||||
import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { roomsQueryKeys, subscriptionsQueryKeys } from '../../../../../../lib/queryKeys';
|
||||
|
||||
export const useReturnChatToQueueMutation = (
|
||||
options?: Omit<UseMutationOptions<void, Error, IRoom['_id']>, 'mutationFn'>,
|
||||
): UseMutationResult<void, Error, IRoom['_id']> => {
|
||||
const returnChatToQueue = useEndpoint('POST', '/v1/livechat/inquiries.returnAsInquiry');
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (rid) => {
|
||||
await returnChatToQueue({ roomId: rid });
|
||||
},
|
||||
...options,
|
||||
onSuccess: async (data, rid, context) => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['current-chats'] });
|
||||
queryClient.removeQueries({ queryKey: roomsQueryKeys.room(rid) });
|
||||
queryClient.removeQueries({ queryKey: roomsQueryKeys.info(rid) });
|
||||
queryClient.removeQueries({ queryKey: subscriptionsQueryKeys.subscription(rid) });
|
||||
return options?.onSuccess?.(data, rid, context);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import { lazy } from 'react';
|
||||
|
||||
export default lazy(() => import('./QuickActions'));
|
||||
Reference in New Issue
Block a user