From beb6aa8f563a9ba3334c33178095616104487bc8 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Fri, 16 Aug 2024 16:12:21 -0300 Subject: [PATCH 1/6] refactor: `Realtime Monitoring/Chart` component to TS --- .../analytics/InterchangeableChart.tsx | 2 +- .../realTimeMonitoring/charts/AgentStatusChart.js | 2 +- .../realTimeMonitoring/charts/Chart.js | 15 --------------- .../realTimeMonitoring/charts/Chart.tsx | 14 ++++++++++++++ .../charts/ChatDurationChart.js | 2 +- .../realTimeMonitoring/charts/ChatsChart.js | 2 +- .../charts/ChatsPerAgentChart.js | 2 +- .../charts/ChatsPerDepartmentChart.js | 2 +- .../charts/ResponseTimesChart.js | 2 +- 9 files changed, 21 insertions(+), 22 deletions(-) delete mode 100644 apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.js create mode 100644 apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx diff --git a/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx b/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx index 872bb4f05b0d..e51cacf76c83 100644 --- a/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx +++ b/apps/meteor/client/views/omnichannel/analytics/InterchangeableChart.tsx @@ -94,7 +94,7 @@ const InterchangeableChart = ({ }); }, [chartName, departmentId, draw, end, start, t, loadData]); - return ; + return ; }; export default InterchangeableChart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js index 4564a859ccf5..4724bea74350 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js @@ -60,7 +60,7 @@ const AgentStatusChart = ({ params, reloadRef, ...props }) => { } }, [available, away, busy, offline, state, t, updateChartData]); - return ; + return ; }; export default AgentStatusChart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.js deleted file mode 100644 index 8ba5066c1706..000000000000 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import React, { forwardRef } from 'react'; - -const style = { - minHeight: '250px', -}; -const Chart = forwardRef(function Chart(props, ref) { - return ( - - - - ); -}); - -export default Chart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx new file mode 100644 index 000000000000..84036fd44147 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx @@ -0,0 +1,14 @@ +import { Box } from '@rocket.chat/fuselage'; +import type { MutableRefObject } from 'react'; +import React from 'react'; + +const style = { + minHeight: '250px', +}; +const Chart = ({ canvasRef, ...props }: { canvasRef: MutableRefObject }) => ( + + + +); + +export default Chart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js index d85fe1d3799d..b4e155394f68 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js @@ -72,7 +72,7 @@ const ChatDurationChart = ({ params, reloadRef, ...props }) => { } }, [avg, longest, state, t, updateChartData]); - return ; + return ; }; export default ChatDurationChart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js index cbe1285931d7..5a540dcd2dbd 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js @@ -60,7 +60,7 @@ const ChatsChart = ({ params, reloadRef, ...props }) => { } }, [closed, open, queued, onhold, state, t, updateChartData]); - return ; + return ; }; export default ChatsChart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js index 6c7741781e1b..48b0bdbf655e 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js @@ -56,7 +56,7 @@ const ChatsPerAgentChart = ({ params, reloadRef, ...props }) => { } }, [chartData, state, t, updateChartData]); - return ; + return ; }; export default ChatsPerAgentChart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js index 030fcedc0576..fbfe91695626 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js @@ -59,7 +59,7 @@ const ChatsPerDepartmentChart = ({ params, reloadRef, ...props }) => { } }, [chartData, state, t, updateChartData]); - return ; + return ; }; export default ChatsPerDepartmentChart; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js index ac0500ebf4da..cfc33687c8fc 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js @@ -78,7 +78,7 @@ const ResponseTimesChart = ({ params, reloadRef, ...props }) => { } }, [reactionAvg, reactionLongest, responseAvg, responseLongest, state, t, updateChartData]); - return ; + return ; }; export default ResponseTimesChart; From 712887af1c1c14692066cf7d1386fc6ccbaf681c Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Mon, 19 Aug 2024 13:55:01 -0300 Subject: [PATCH 2/6] wip --- ...AgentStatusChart.js => AgentStatusChart.tsx} | 17 +++++++++++++---- .../charts/useUpdateChartData.ts | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) rename apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/{AgentStatusChart.js => AgentStatusChart.tsx} (70%) diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx similarity index 70% rename from apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js rename to apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx index 4724bea74350..ff48c572ecb4 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx @@ -1,4 +1,8 @@ +import type { OperationParams } from '@rocket.chat/rest-typings'; import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { Chart as ChartType } from 'chart.js'; +import type { TFunction } from 'i18next'; +import type { MutableRefObject } from 'react'; import React, { useRef, useEffect } from 'react'; import { drawDoughnutChart } from '../../../../../app/livechat/client/lib/chartHandler'; @@ -16,7 +20,7 @@ const initialData = { offline: 0, }; -const init = (canvas, context, t) => +const init = (canvas: HTMLCanvasElement, context: undefined, t: TFunction) => drawDoughnutChart( canvas, t('Agents'), @@ -25,11 +29,16 @@ const init = (canvas, context, t) => Object.values(initialData), ); -const AgentStatusChart = ({ params, reloadRef, ...props }) => { +type AgentStatusChartsProps = { + params: OperationParams<'GET', '/v1/livechat/analytics/dashboards/charts/agents-status'>; + reloadRef: MutableRefObject<{ [x: string]: () => void }>; +}; + +const AgentStatusChart = ({ params, reloadRef, ...props }: AgentStatusChartsProps) => { const t = useTranslation(); - const canvas = useRef(); - const context = useRef(); + const canvas = useRef(null); + const context = useRef(null); const updateChartData = useUpdateChartData({ context, diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts index 805d828a9893..73185d8c4c49 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts @@ -1,13 +1,13 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { type Chart } from 'chart.js'; import { type TFunction } from 'i18next'; -import { type RefObject } from 'react'; +import { type MutableRefObject } from 'react'; import { updateChart } from '../../../../../app/livechat/client/lib/chartHandler'; type UseUpdateChartDataOptions = { - context: RefObject; - canvas: RefObject; + context: MutableRefObject; + canvas: MutableRefObject; init: (canvas: HTMLCanvasElement, context: undefined, t: TFunction) => Promise; t: TFunction; }; From da33ffe2151a231d44d68d6e803d253e188be906 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Mon, 19 Aug 2024 14:30:32 -0300 Subject: [PATCH 3/6] fix review --- .../views/omnichannel/realTimeMonitoring/charts/Chart.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx index 84036fd44147..5a47906ce92d 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/Chart.tsx @@ -2,10 +2,12 @@ import { Box } from '@rocket.chat/fuselage'; import type { MutableRefObject } from 'react'; import React from 'react'; +type ChartProps = { canvasRef: MutableRefObject }; + const style = { minHeight: '250px', }; -const Chart = ({ canvasRef, ...props }: { canvasRef: MutableRefObject }) => ( +const Chart = ({ canvasRef, ...props }: ChartProps) => ( From bf917001c7a7b56f2f06e106a0c9d3a5b3ca1a31 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Mon, 19 Aug 2024 14:54:48 -0300 Subject: [PATCH 4/6] refactor: AgentStatusChart to TS --- .../meteor/app/livechat/client/lib/chartHandler.ts | 12 ++++++------ .../realTimeMonitoring/charts/AgentStatusChart.tsx | 14 ++++++++------ .../charts/useUpdateChartData.ts | 8 ++++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/meteor/app/livechat/client/lib/chartHandler.ts b/apps/meteor/app/livechat/client/lib/chartHandler.ts index 19c1a004ca22..a33076532ef5 100644 --- a/apps/meteor/app/livechat/client/lib/chartHandler.ts +++ b/apps/meteor/app/livechat/client/lib/chartHandler.ts @@ -177,10 +177,10 @@ export const drawDoughnutChart = async ( chartContext: { destroy: () => void } | undefined, dataLabels: string[], dataPoints: number[], -): Promise | void> => { +): Promise => { if (!chart) { console.error('No chart element'); - return; + throw new Error('No chart element'); } if (chartContext) { chartContext.destroy(); @@ -200,7 +200,7 @@ export const drawDoughnutChart = async ( ], }, options: doughnutChartConfiguration(title), - }); + }) as ChartType; }; /** @@ -209,12 +209,12 @@ export const drawDoughnutChart = async ( * @param {String} label [chart label] * @param {Array(Double)} data [updated data] */ -export const updateChart = async (c: ChartType, label: string, data: { [x: string]: number }): Promise => { +export const updateChart = async (c: ChartType, label: string, data: number[]): Promise => { const chart = await c; if (chart.data?.labels?.indexOf(label) === -1) { // insert data chart.data.labels.push(label); - chart.data.datasets.forEach((dataset: { data: any[] }, idx: string | number) => { + chart.data.datasets.forEach((dataset: { data: any[] }, idx: number) => { dataset.data.push(data[idx]); }); } else { @@ -224,7 +224,7 @@ export const updateChart = async (c: ChartType, label: string, data: { [x: strin return; } - chart.data.datasets.forEach((dataset: { data: { [x: string]: any } }, idx: string | number) => { + chart.data.datasets.forEach((dataset: { data: { [x: string]: any } }, idx: number) => { dataset.data[index] = data[idx]; }); } diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx index ff48c572ecb4..f1b2d0eed337 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.tsx @@ -1,7 +1,7 @@ import type { OperationParams } from '@rocket.chat/rest-typings'; +import type { TranslationContextValue, TranslationKey } from '@rocket.chat/ui-contexts'; import { useTranslation } from '@rocket.chat/ui-contexts'; import type { Chart as ChartType } from 'chart.js'; -import type { TFunction } from 'i18next'; import type { MutableRefObject } from 'react'; import React, { useRef, useEffect } from 'react'; @@ -20,12 +20,12 @@ const initialData = { offline: 0, }; -const init = (canvas: HTMLCanvasElement, context: undefined, t: TFunction) => +const init = (canvas: HTMLCanvasElement, context: ChartType | undefined, t: TranslationContextValue['translate']): Promise => drawDoughnutChart( canvas, t('Agents'), context, - labels.map((l) => t(l)), + labels.map((l) => t(l as TranslationKey)), Object.values(initialData), ); @@ -37,8 +37,8 @@ type AgentStatusChartsProps = { const AgentStatusChart = ({ params, reloadRef, ...props }: AgentStatusChartsProps) => { const t = useTranslation(); - const canvas = useRef(null); - const context = useRef(null); + const canvas: MutableRefObject = useRef(null); + const context: MutableRefObject = useRef(); const updateChartData = useUpdateChartData({ context, @@ -55,7 +55,9 @@ const AgentStatusChart = ({ params, reloadRef, ...props }: AgentStatusChartsProp useEffect(() => { const initChart = async () => { - context.current = await init(canvas.current, context.current, t); + if (canvas?.current) { + context.current = await init(canvas.current, context.current, t); + } }; initChart(); }, [t]); diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts index 73185d8c4c49..d942b93a4223 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/useUpdateChartData.ts @@ -1,6 +1,6 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import type { TranslationContextValue } from '@rocket.chat/ui-contexts'; import { type Chart } from 'chart.js'; -import { type TFunction } from 'i18next'; import { type MutableRefObject } from 'react'; import { updateChart } from '../../../../../app/livechat/client/lib/chartHandler'; @@ -8,12 +8,12 @@ import { updateChart } from '../../../../../app/livechat/client/lib/chartHandler type UseUpdateChartDataOptions = { context: MutableRefObject; canvas: MutableRefObject; - init: (canvas: HTMLCanvasElement, context: undefined, t: TFunction) => Promise; - t: TFunction; + init: (canvas: HTMLCanvasElement, context: undefined, t: TranslationContextValue['translate']) => Promise; + t: TranslationContextValue['translate']; }; export const useUpdateChartData = ({ context: contextRef, canvas: canvasRef, init, t }: UseUpdateChartDataOptions) => - useMutableCallback(async (label: string, data: { [x: string]: number }) => { + useMutableCallback(async (label: string, data: number[]) => { const canvas = canvasRef.current; if (!canvas) { From 2855861b8a2ba3546fb921c1e2853dc48f3ef5c9 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Tue, 20 Aug 2024 09:46:35 -0300 Subject: [PATCH 5/6] refactor: `ChatsChart` to TS --- .../charts/{ChatsChart.js => ChatsChart.tsx} | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) rename apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/{ChatsChart.js => ChatsChart.tsx} (60%) diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx similarity index 60% rename from apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js rename to apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx index 5a540dcd2dbd..55f738a80f7b 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx @@ -1,4 +1,8 @@ +import type { OperationParams } from '@rocket.chat/rest-typings'; +import type { TranslationContextValue, TranslationKey } from '@rocket.chat/ui-contexts'; import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { Chart as ChartType } from 'chart.js'; +import type { MutableRefObject } from 'react'; import React, { useRef, useEffect } from 'react'; import { drawDoughnutChart } from '../../../../../app/livechat/client/lib/chartHandler'; @@ -16,20 +20,25 @@ const initialData = { closed: 0, }; -const init = (canvas, context, t) => +const init = (canvas: HTMLCanvasElement, context: ChartType | undefined, t: TranslationContextValue['translate']) => drawDoughnutChart( canvas, - t('Chats'), + t('Chats' as TranslationKey), context, - labels.map((l) => t(l)), + labels.map((l) => t(l as TranslationKey)), Object.values(initialData), ); -const ChatsChart = ({ params, reloadRef, ...props }) => { +type ChatsChartProps = { + params: OperationParams<'GET', '/v1/livechat/analytics/dashboards/charts/chats'>; + reloadRef: MutableRefObject<{ [x: string]: () => void }>; +}; + +const ChatsChart = ({ params, reloadRef, ...props }: ChatsChartProps) => { const t = useTranslation(); - const canvas = useRef(); - const context = useRef(); + const canvas: MutableRefObject = useRef(null); + const context: MutableRefObject = useRef(); const updateChartData = useUpdateChartData({ context, @@ -46,7 +55,9 @@ const ChatsChart = ({ params, reloadRef, ...props }) => { useEffect(() => { const initChart = async () => { - context.current = await init(canvas.current, context.current, t); + if (canvas?.current) { + context.current = await init(canvas.current, context.current, t); + } }; initChart(); }, [t]); From ded36e90a39ecd8ba3322576ec8ea041b7b2af03 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Fri, 23 Aug 2024 11:21:53 -0300 Subject: [PATCH 6/6] fix reviews --- apps/meteor/app/livechat/client/lib/chartHandler.ts | 1 - .../views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx | 2 +- packages/i18n/src/locales/en.i18n.json | 1 + packages/i18n/src/locales/pt-BR.i18n.json | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/client/lib/chartHandler.ts b/apps/meteor/app/livechat/client/lib/chartHandler.ts index a33076532ef5..da2d4be3735c 100644 --- a/apps/meteor/app/livechat/client/lib/chartHandler.ts +++ b/apps/meteor/app/livechat/client/lib/chartHandler.ts @@ -179,7 +179,6 @@ export const drawDoughnutChart = async ( dataPoints: number[], ): Promise => { if (!chart) { - console.error('No chart element'); throw new Error('No chart element'); } if (chartContext) { diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx index 55f738a80f7b..b84dfe6918a2 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.tsx @@ -23,7 +23,7 @@ const initialData = { const init = (canvas: HTMLCanvasElement, context: ChartType | undefined, t: TranslationContextValue['translate']) => drawDoughnutChart( canvas, - t('Chats' as TranslationKey), + t('Chats'), context, labels.map((l) => t(l as TranslationKey)), Object.values(initialData), diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index c270bb9bffb1..158d12dd3201 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -986,6 +986,7 @@ "Channels_list": "List of public channels", "Channel_what_is_this_channel_about": "What is this channel about?", "Chart": "Chart", + "Chats": "Chats", "Chat_button": "Chat button", "Chat_close": "Chat Close", "Chat_closed": "Chat closed", diff --git a/packages/i18n/src/locales/pt-BR.i18n.json b/packages/i18n/src/locales/pt-BR.i18n.json index 67c8f46888ad..100b0e06ac28 100644 --- a/packages/i18n/src/locales/pt-BR.i18n.json +++ b/packages/i18n/src/locales/pt-BR.i18n.json @@ -833,6 +833,7 @@ "Channels_list": "Lista de canais públicos", "Channel_what_is_this_channel_about": "Sobre o que é este canal?", "Chart": "Gráfico", + "Chats": "Conversas", "Chat_button": "Botão da conversa", "Chat_close": "Fechar conversa", "Chat_closed": "Conversa encerrada",