From c1976240934dde1e88c57dbca3a64788bd9a4e85 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Thu, 22 Jun 2023 14:38:31 -0300 Subject: [PATCH] chore: Remove old deprecated `GenericTable` in favor of v2 (#29594) --- .../GenericTable/GenericTable.stories.tsx | 87 +++++-- .../components/GenericTable/GenericTable.tsx | 132 ++-------- .../GenericTable/GenericTableBody.tsx | 5 + .../GenericTable/GenericTableCell.tsx | 5 + .../GenericTable/GenericTableHeader.tsx | 11 + .../{V2 => }/GenericTableHeaderCell.tsx | 8 +- .../{V2 => }/GenericTableLoadingRow.tsx | 20 +- .../{V2 => }/GenericTableLoadingTable.tsx | 0 .../GenericTable/GenericTableRow.tsx | 5 + .../components/GenericTable/HeaderCell.tsx | 27 -- .../GenericTable/V2/GenericTable.tsx | 23 -- .../GenericTable/V2/GenericTableBody.tsx | 5 - .../GenericTable/V2/GenericTableCell.tsx | 5 - .../GenericTable/V2/GenericTableHeader.tsx | 11 - .../GenericTable/V2/GenericTableRow.tsx | 5 - .../client/components/GenericTable/index.ts | 23 +- .../CustomSoundsTable/CustomSoundRow.tsx | 3 +- .../PermissionsTable/RoleHeader.tsx | 6 +- .../groups/voip/VoipExtensionsPage.tsx | 187 ++++++++------ .../businessHours/BusinessHoursPage.js | 5 +- .../currentChats/CurrentChatsRoute.tsx | 2 +- .../omnichannel/directory/calls/CallTable.tsx | 219 ++++++++-------- .../directory/calls/CallTableRow.tsx | 4 +- .../omnichannel/directory/chats/ChatTable.tsx | 241 +++++++++--------- .../{ContactTab.js => ContactTab.tsx} | 6 +- .../directory/contacts/ContactTable.tsx | 158 ++++++------ .../omnichannel/queueList/QueueListPage.tsx | 55 +--- .../omnichannel/queueList/QueueListTable.tsx | 145 +++++++++++ .../views/omnichannel/queueList/index.ts | 1 + .../views/omnichannel/queueList/index.tsx | 114 --------- .../info/Delete/ChannelDeletionTable.js | 81 ------ .../info/Delete/ChannelDeletionTable.tsx | 78 ++++++ .../info/Delete/ChannelDeletionTableRow.tsx | 35 +++ .../contextualBar/info/Delete/ChannelRow.js | 29 --- .../ee/client/omnichannel/BusinessHoursRow.js | 18 +- .../client/omnichannel/BusinessHoursTable.js | 103 ++++++-- .../BusinessHoursTable.stories.tsx | 2 +- .../BusinessHoursTableContainer.js | 43 ---- .../cannedResponses/CannedResponsesPage.tsx | 75 ++---- .../cannedResponses/CannedResponsesRoute.tsx | 219 +--------------- .../cannedResponses/CannedResponsesTable.tsx | 203 +++++++++++++++ .../RemoveCannedResponseButton.tsx | 32 +-- .../omnichannel/priorities/PrioritiesPage.tsx | 4 +- .../priorities/PrioritiesTable.tsx | 72 ++++-- 44 files changed, 1188 insertions(+), 1324 deletions(-) create mode 100644 apps/meteor/client/components/GenericTable/GenericTableBody.tsx create mode 100644 apps/meteor/client/components/GenericTable/GenericTableCell.tsx create mode 100644 apps/meteor/client/components/GenericTable/GenericTableHeader.tsx rename apps/meteor/client/components/GenericTable/{V2 => }/GenericTableHeaderCell.tsx (81%) rename apps/meteor/client/components/GenericTable/{V2 => }/GenericTableLoadingRow.tsx (52%) rename apps/meteor/client/components/GenericTable/{V2 => }/GenericTableLoadingTable.tsx (100%) create mode 100644 apps/meteor/client/components/GenericTable/GenericTableRow.tsx delete mode 100644 apps/meteor/client/components/GenericTable/HeaderCell.tsx delete mode 100644 apps/meteor/client/components/GenericTable/V2/GenericTable.tsx delete mode 100644 apps/meteor/client/components/GenericTable/V2/GenericTableBody.tsx delete mode 100644 apps/meteor/client/components/GenericTable/V2/GenericTableCell.tsx delete mode 100644 apps/meteor/client/components/GenericTable/V2/GenericTableHeader.tsx delete mode 100644 apps/meteor/client/components/GenericTable/V2/GenericTableRow.tsx rename apps/meteor/client/views/omnichannel/directory/contacts/{ContactTab.js => ContactTab.tsx} (82%) create mode 100644 apps/meteor/client/views/omnichannel/queueList/QueueListTable.tsx create mode 100644 apps/meteor/client/views/omnichannel/queueList/index.ts delete mode 100644 apps/meteor/client/views/omnichannel/queueList/index.tsx delete mode 100644 apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.js create mode 100644 apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx create mode 100644 apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx delete mode 100644 apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelRow.js delete mode 100644 apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js create mode 100644 apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx diff --git a/apps/meteor/client/components/GenericTable/GenericTable.stories.tsx b/apps/meteor/client/components/GenericTable/GenericTable.stories.tsx index 3ebb67b6e09..f563999b072 100644 --- a/apps/meteor/client/components/GenericTable/GenericTable.stories.tsx +++ b/apps/meteor/client/components/GenericTable/GenericTable.stories.tsx @@ -1,8 +1,17 @@ -import { TextInput, Box, Icon, Table } from '@rocket.chat/fuselage'; +import { TextInput, Box, Icon } from '@rocket.chat/fuselage'; import type { ComponentMeta, ComponentStory } from '@storybook/react'; import React from 'react'; -import GenericTable from '.'; +import { + GenericTable, + GenericTableHeaderCell, + GenericTableCell, + GenericTableRow, + GenericTableHeader, + GenericTableBody, + GenericTableLoadingTable, +} from '.'; +import GenericNoResults from '../GenericNoResults/GenericNoResults'; export default { title: 'Components/GenericTable', @@ -16,33 +25,59 @@ export default { ], } as ComponentMeta; -const header = [ - Name, - Email, -]; +const headers = ( + <> + Name + Email + +); + +const results = Array.from({ length: 10 }, (_, i) => ({ + _id: i, + name: `John Doe #${i}`, + email: `john.doe.n${i}@example.com`, +})); + +const filter = ( + <> + + } /> + + +); -const renderFilter = () => ( - - } /> - +export const Default: ComponentStory = () => ( + <> + {filter} + + {headers} + + {results?.map(({ _id, name, email }: any) => ( + + {name} + {email} + + ))} + + + ); -const renderRow = ({ _id, name, email }: any) => ( - - {name} - {email} - +export const Loading: ComponentStory = () => ( + <> + {filter} + + {headers} + + + + + ); -export const Default: ComponentStory = (args) => ( - +export const NoResults: ComponentStory = () => ( + <> + {filter} + + ); -Default.storyName = 'GenericTable'; -Default.args = { - results: Array.from({ length: 10 }, (_, i) => ({ - _id: i, - name: `John Doe #${i}`, - email: `john.doe.n${i}@example.com`, - })), - total: 1, -}; diff --git a/apps/meteor/client/components/GenericTable/GenericTable.tsx b/apps/meteor/client/components/GenericTable/GenericTable.tsx index ef010362490..ae33cce07a3 100644 --- a/apps/meteor/client/components/GenericTable/GenericTable.tsx +++ b/apps/meteor/client/components/GenericTable/GenericTable.tsx @@ -1,121 +1,23 @@ -import { Pagination, Tile } from '@rocket.chat/fuselage'; -import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactNode, ReactElement, Key, Ref, RefAttributes } from 'react'; -import React, { useState, useEffect, forwardRef, useMemo } from 'react'; -import flattenChildren from 'react-keyed-flatten-children'; +import { Box, Table } from '@rocket.chat/fuselage'; +import type { ReactNode, TableHTMLAttributes } from 'react'; +import React, { forwardRef } from 'react'; -import { GenericTable as GenericTableV2 } from './V2/GenericTable'; -import { GenericTableBody } from './V2/GenericTableBody'; -import { GenericTableHeader } from './V2/GenericTableHeader'; -import { GenericTableLoadingTable } from './V2/GenericTableLoadingTable'; -import { usePagination } from './hooks/usePagination'; +import ScrollableContentWrapper from '../ScrollableContentWrapper'; -const defaultParamsValue = { text: '', current: 0, itemsPerPage: 25 } as const; -const defaultSetParamsValue = (): void => undefined; - -export type GenericTableParams = { - text?: string; - current: number; - itemsPerPage: 25 | 50 | 100; -}; - -type GenericTableProps void }, ResultProps> = { +type GenericTableProps = { fixed?: boolean; - header?: ReactNode; - params?: GenericTableParams; - setParams?: React.Dispatch>; - children?: (props: ResultProps, key: number) => ReactElement; - renderFilter?: (props: FilterProps) => ReactElement; - renderRow?: (props: ResultProps) => ReactElement; - results?: ResultProps[]; - total?: number; - pagination?: boolean; -} & FilterProps; - -const GenericTable = forwardRef(function GenericTable< - FilterProps extends { onChange?: (params: GenericTableParams) => void }, - ResultProps extends { _id?: Key } | object, ->( - { - children, - fixed = true, - header, - params: paramsDefault = defaultParamsValue, - setParams = defaultSetParamsValue, - renderFilter, - renderRow: RenderRowComponent, - results, - total, - pagination = true, - ...props - }: GenericTableProps, - ref: Ref, -) { - const t = useTranslation(); - - const [filter, setFilter] = useState(paramsDefault); - - const { itemsPerPage, setItemsPerPage, current, setCurrent, itemsPerPageLabel, showingResultsLabel } = usePagination(); - - const params = useDebouncedValue(filter, 500); - - useEffect(() => { - setParams((prevParams) => { - setCurrent(prevParams.text === params.text ? current : 0); - - return { - ...params, - text: params.text || '', - current: prevParams.text === params.text ? current : 0, - itemsPerPage, - }; - }); - }, [params, current, itemsPerPage, setParams, setCurrent, setItemsPerPage]); - - const headerCells = useMemo(() => flattenChildren(header).length, [header]); - - const isLoading = !results; + children: ReactNode; +} & TableHTMLAttributes; +export const GenericTable = forwardRef(function GenericTable({ fixed = true, children, ...props }, ref) { return ( - <> - {typeof renderFilter === 'function' - ? renderFilter({ ...props, onChange: setFilter } as any) // TODO: ugh - : null} - {results && !results.length ? ( - - {t('No_data_found')} - - ) : ( - <> - - {header && {header}} - - {isLoading && } - {!isLoading && - ((RenderRowComponent && - results?.map((props, index) => )) || - (children && results?.map(children)))} - - - {pagination && ( - - )} - - )} - + + + {/* TODO: Fix fuselage */} + + {children} +
+
+
); -}) as void }, TResultProps extends { _id?: Key } | object>( - props: GenericTableProps & RefAttributes, -) => ReactElement | null; - -export default GenericTable; +}); diff --git a/apps/meteor/client/components/GenericTable/GenericTableBody.tsx b/apps/meteor/client/components/GenericTable/GenericTableBody.tsx new file mode 100644 index 00000000000..3b68ccff94a --- /dev/null +++ b/apps/meteor/client/components/GenericTable/GenericTableBody.tsx @@ -0,0 +1,5 @@ +import { TableBody } from '@rocket.chat/fuselage'; +import type { FC, ComponentProps } from 'react'; +import React from 'react'; + +export const GenericTableBody: FC> = (props) => ; diff --git a/apps/meteor/client/components/GenericTable/GenericTableCell.tsx b/apps/meteor/client/components/GenericTable/GenericTableCell.tsx new file mode 100644 index 00000000000..8b783c1a720 --- /dev/null +++ b/apps/meteor/client/components/GenericTable/GenericTableCell.tsx @@ -0,0 +1,5 @@ +import { TableCell } from '@rocket.chat/fuselage'; +import type { ComponentProps, FC } from 'react'; +import React from 'react'; + +export const GenericTableCell: FC> = (props) => ; diff --git a/apps/meteor/client/components/GenericTable/GenericTableHeader.tsx b/apps/meteor/client/components/GenericTable/GenericTableHeader.tsx new file mode 100644 index 00000000000..2dbbaade648 --- /dev/null +++ b/apps/meteor/client/components/GenericTable/GenericTableHeader.tsx @@ -0,0 +1,11 @@ +import { TableHead } from '@rocket.chat/fuselage'; +import type { FC, ComponentProps } from 'react'; +import React from 'react'; + +import { GenericTableRow } from './GenericTableRow'; + +export const GenericTableHeader: FC> = ({ children, ...props }) => ( + + {children} + +); diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTableHeaderCell.tsx b/apps/meteor/client/components/GenericTable/GenericTableHeaderCell.tsx similarity index 81% rename from apps/meteor/client/components/GenericTable/V2/GenericTableHeaderCell.tsx rename to apps/meteor/client/components/GenericTable/GenericTableHeaderCell.tsx index 5127d38a834..216fb8997d1 100644 --- a/apps/meteor/client/components/GenericTable/V2/GenericTableHeaderCell.tsx +++ b/apps/meteor/client/components/GenericTable/GenericTableHeaderCell.tsx @@ -1,8 +1,8 @@ -import { Box, Table } from '@rocket.chat/fuselage'; +import { Box, TableCell } from '@rocket.chat/fuselage'; import type { ComponentProps, ReactElement } from 'react'; import React, { useCallback } from 'react'; -import SortIcon from '../SortIcon'; +import SortIcon from './SortIcon'; type GenericTableHeaderCellProps = Omit, 'onClick'> & { active?: boolean; @@ -21,11 +21,11 @@ export const GenericTableHeaderCell = ({ }: GenericTableHeaderCellProps): ReactElement => { const fn = useCallback(() => onClick && sort && onClick(sort), [sort, onClick]); return ( - + {children} {sort && } - + ); }; diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTableLoadingRow.tsx b/apps/meteor/client/components/GenericTable/GenericTableLoadingRow.tsx similarity index 52% rename from apps/meteor/client/components/GenericTable/V2/GenericTableLoadingRow.tsx rename to apps/meteor/client/components/GenericTable/GenericTableLoadingRow.tsx index c0aee3d68cc..5c539e732b3 100644 --- a/apps/meteor/client/components/GenericTable/V2/GenericTableLoadingRow.tsx +++ b/apps/meteor/client/components/GenericTable/GenericTableLoadingRow.tsx @@ -1,14 +1,10 @@ -import { Box, Skeleton, Table } from '@rocket.chat/fuselage'; +import { Box, Skeleton, TableRow, TableCell } from '@rocket.chat/fuselage'; import type { ReactElement } from 'react'; import React from 'react'; -type GenericTableLoadingRowRowProps = { - cols: number; -}; - -export const GenericTableLoadingRow = ({ cols }: GenericTableLoadingRowRowProps): ReactElement => ( - - +export const GenericTableLoadingRow = ({ cols }: { cols: number }): ReactElement => ( + + @@ -16,11 +12,11 @@ export const GenericTableLoadingRow = ({ cols }: GenericTableLoadingRowRowProps) - + {Array.from({ length: cols - 1 }, (_, i) => ( - + - + ))} - + ); diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTableLoadingTable.tsx b/apps/meteor/client/components/GenericTable/GenericTableLoadingTable.tsx similarity index 100% rename from apps/meteor/client/components/GenericTable/V2/GenericTableLoadingTable.tsx rename to apps/meteor/client/components/GenericTable/GenericTableLoadingTable.tsx diff --git a/apps/meteor/client/components/GenericTable/GenericTableRow.tsx b/apps/meteor/client/components/GenericTable/GenericTableRow.tsx new file mode 100644 index 00000000000..6db18a8bfd1 --- /dev/null +++ b/apps/meteor/client/components/GenericTable/GenericTableRow.tsx @@ -0,0 +1,5 @@ +import { TableRow } from '@rocket.chat/fuselage'; +import type { ComponentProps, FC } from 'react'; +import React from 'react'; + +export const GenericTableRow: FC> = (props) => ; diff --git a/apps/meteor/client/components/GenericTable/HeaderCell.tsx b/apps/meteor/client/components/GenericTable/HeaderCell.tsx deleted file mode 100644 index 22ee663792e..00000000000 --- a/apps/meteor/client/components/GenericTable/HeaderCell.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Box, Table } from '@rocket.chat/fuselage'; -import type { ComponentProps, FC } from 'react'; -import React, { useCallback } from 'react'; - -import SortIcon from './SortIcon'; - -type HeaderCellProps = { - active?: boolean; - direction?: 'asc' | 'desc'; - sort?: string; - clickable?: boolean; - onClick?: (sort: string) => void; -} & Omit, 'onClick'>; - -const HeaderCell: FC = ({ children, active, direction, sort, onClick, ...props }) => { - const fn = useCallback(() => onClick && sort && onClick(sort), [sort, onClick]); - return ( - - - {children} - {sort && } - - - ); -}; - -export default HeaderCell; diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTable.tsx b/apps/meteor/client/components/GenericTable/V2/GenericTable.tsx deleted file mode 100644 index 5b99249e3b3..00000000000 --- a/apps/meteor/client/components/GenericTable/V2/GenericTable.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Box, Table } from '@rocket.chat/fuselage'; -import type { ReactNode, TableHTMLAttributes } from 'react'; -import React, { forwardRef } from 'react'; - -import ScrollableContentWrapper from '../../ScrollableContentWrapper'; - -type GenericTableProps = { - fixed?: boolean; - children: ReactNode; -} & TableHTMLAttributes; - -export const GenericTable = forwardRef(function GenericTable({ fixed = true, children, ...props }, ref) { - return ( - - - {/* TODO: Fix fuselage */} - - {children} -
-
-
- ); -}); diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTableBody.tsx b/apps/meteor/client/components/GenericTable/V2/GenericTableBody.tsx deleted file mode 100644 index af57242bbc9..00000000000 --- a/apps/meteor/client/components/GenericTable/V2/GenericTableBody.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Table } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; -import React from 'react'; - -export const GenericTableBody: FC = (props) => ; diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTableCell.tsx b/apps/meteor/client/components/GenericTable/V2/GenericTableCell.tsx deleted file mode 100644 index 8558920c54c..00000000000 --- a/apps/meteor/client/components/GenericTable/V2/GenericTableCell.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Table } from '@rocket.chat/fuselage'; -import type { ComponentProps, FC } from 'react'; -import React from 'react'; - -export const GenericTableCell: FC> = (props) => ; diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTableHeader.tsx b/apps/meteor/client/components/GenericTable/V2/GenericTableHeader.tsx deleted file mode 100644 index 847f32232d8..00000000000 --- a/apps/meteor/client/components/GenericTable/V2/GenericTableHeader.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Table } from '@rocket.chat/fuselage'; -import type { FC } from 'react'; -import React from 'react'; - -import { GenericTableRow } from './GenericTableRow'; - -export const GenericTableHeader: FC = ({ children, ...props }) => ( - - {children} - -); diff --git a/apps/meteor/client/components/GenericTable/V2/GenericTableRow.tsx b/apps/meteor/client/components/GenericTable/V2/GenericTableRow.tsx deleted file mode 100644 index 56267adfb15..00000000000 --- a/apps/meteor/client/components/GenericTable/V2/GenericTableRow.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Table } from '@rocket.chat/fuselage'; -import type { ComponentProps, FC } from 'react'; -import React from 'react'; - -export const GenericTableRow: FC> = (props) => ; diff --git a/apps/meteor/client/components/GenericTable/index.ts b/apps/meteor/client/components/GenericTable/index.ts index 3a515044302..29855bb106e 100644 --- a/apps/meteor/client/components/GenericTable/index.ts +++ b/apps/meteor/client/components/GenericTable/index.ts @@ -1,15 +1,8 @@ -import GenericTable from './GenericTable'; -import HeaderCell from './HeaderCell'; - -export default Object.assign(GenericTable, { - HeaderCell, -}); - -export * from './V2/GenericTable'; -export * from './V2/GenericTableBody'; -export * from './V2/GenericTableCell'; -export * from './V2/GenericTableHeader'; -export * from './V2/GenericTableHeaderCell'; -export * from './V2/GenericTableLoadingRow'; -export * from './V2/GenericTableLoadingTable'; -export * from './V2/GenericTableRow'; +export * from './GenericTable'; +export * from './GenericTableBody'; +export * from './GenericTableCell'; +export * from './GenericTableHeader'; +export * from './GenericTableHeaderCell'; +export * from './GenericTableLoadingRow'; +export * from './GenericTableLoadingTable'; +export * from './GenericTableRow'; diff --git a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundRow.tsx b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundRow.tsx index 139c27afc07..789f07f0c1c 100644 --- a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundRow.tsx +++ b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundRow.tsx @@ -3,8 +3,7 @@ import { useCustomSound, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useCallback, useState } from 'react'; -import { GenericTableCell } from '../../../../components/GenericTable/V2/GenericTableCell'; -import { GenericTableRow } from '../../../../components/GenericTable/V2/GenericTableRow'; +import { GenericTableCell, GenericTableRow } from '../../../../components/GenericTable'; type CustomSoundRowProps = { onClick: (soundId: string) => () => void; diff --git a/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx b/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx index fc801bbd4c5..f1b637c1e0d 100644 --- a/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx +++ b/apps/meteor/client/views/admin/permissions/PermissionsTable/RoleHeader.tsx @@ -5,7 +5,7 @@ import { useRoute } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; -import GenericTable from '../../../../components/GenericTable'; +import { GenericTableHeaderCell } from '../../../../components/GenericTable'; type RoleHeaderProps = { _id: IRole['_id']; @@ -24,14 +24,14 @@ const RoleHeader = ({ _id, name, description }: RoleHeaderProps): ReactElement = }); return ( - + - + ); }; diff --git a/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx b/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx index 62d7144329d..a27ea9ac156 100644 --- a/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx +++ b/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx @@ -1,27 +1,31 @@ -import { Box, Chip, Table, Button } from '@rocket.chat/fuselage'; -import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; -import type { FC } from 'react'; -import React, { useMemo, useCallback, useState } from 'react'; +import { Box, Chip, Button, Pagination } from '@rocket.chat/fuselage'; +import { useSetModal, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import React, { useMemo } from 'react'; -import GenericTable from '../../../../../components/GenericTable'; +import GenericNoResults from '../../../../../components/GenericNoResults'; +import { + GenericTable, + GenericTableHeaderCell, + GenericTableRow, + GenericTableCell, + GenericTableHeader, + GenericTableBody, + GenericTableLoadingRow, +} from '../../../../../components/GenericTable'; +import { usePagination } from '../../../../../components/GenericTable/hooks/usePagination'; import Page from '../../../../../components/Page'; import UserAvatar from '../../../../../components/avatar/UserAvatar'; -import { useEndpointData } from '../../../../../hooks/useEndpointData'; import AssignAgentButton from './AssignAgentButton'; import AssignAgentModal from './AssignAgentModal'; import RemoveAgentButton from './RemoveAgentButton'; -const VoipExtensionsPage: FC = () => { +const VoipExtensionsPage = () => { const t = useTranslation(); const setModal = useSetModal(); - const [params, setParams] = useState<{ current: number; itemsPerPage: 25 | 50 | 100 }>({ - current: 0, - itemsPerPage: 25, - }); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); - const { itemsPerPage, current } = useDebouncedValue(params, 500); const query = useMemo( () => ({ ...(itemsPerPage && { count: itemsPerPage }), @@ -30,66 +34,27 @@ const VoipExtensionsPage: FC = () => { [itemsPerPage, current], ); - const { value: data, reload } = useEndpointData('/v1/omnichannel/extensions', { params: query }); - - const header = useMemo( - () => - [ - - {t('Extension_Number')} - , - - {t('Agent_Name')} - , - - {t('Extension_Status')} - , - - {t('Queues')} - , - , - ].filter(Boolean), - [t], - ); + const getExtensions = useEndpoint('GET', '/v1/omnichannel/extensions'); + const { data, isSuccess, isLoading, refetch } = useQuery(['omnichannel-extensions', query], async () => getExtensions(query), { + refetchOnWindowFocus: false, + }); - const renderRow = useCallback( - ({ _id, extension, username, name, state, queues }) => ( - - {extension} - - {username ? ( - - - - - - {name || username} - - - - - ) : ( - t('Free') - )} - - {state} - - - {queues?.map( - (queue: string, index: number) => - index <= 1 && ( - - {queue} - - ), - )} - {queues?.length > 2 && `+${(queues.length - 2).toString()}`} - - - {username ? : } - - ), - [reload, t], + const headers = ( + <> + + {t('Extension_Number')} + + + {t('Agent_Name')} + + + {t('Extension_Status')} + + + {t('Queues')} + + + ); return ( @@ -99,20 +64,78 @@ const VoipExtensionsPage: FC = () => { {data?.total} {t('Extensions')} - - ({ _id: extension.extension, ...extension }))} - total={data?.total} - params={params} - setParams={setParams} - // renderFilter={({ onChange, ...props }) => } - /> + {isLoading && ( + + {headers} + + + + + )} + {isSuccess && data?.extensions.length === 0 && } + {isSuccess && data?.extensions.length > 0 && ( + <> + + {headers} + + {data?.extensions.map(({ extension, username, name, state, queues }) => ( + + {extension} + + {username ? ( + + + + + + {name || username} + + + + + ) : ( + t('Free') + )} + + {state} + + + {queues?.map( + (queue: string, index: number) => + index <= 1 && ( + + {queue} + + ), + )} + {queues && queues?.length > 2 && `+${(queues.length - 2).toString()}`} + + + {username ? ( + + ) : ( + + )} + + ))} + + + + + )} ); }; diff --git a/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursPage.js b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursPage.js index 801001d441d..8dbcd473bd5 100644 --- a/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursPage.js +++ b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursPage.js @@ -7,10 +7,9 @@ import Page from '../../../components/Page'; const BusinessHoursPage = () => { const t = useTranslation(); - const router = useRoute('omnichannel-businessHours'); - const Table = useMemo(() => lazy(() => import('../../../../ee/client/omnichannel/BusinessHoursTableContainer')), []); + const BusinessHoursTable = useMemo(() => lazy(() => import('../../../../ee/client/omnichannel/BusinessHoursTable')), []); const handleNew = useMutableCallback(() => { router.push({ @@ -28,7 +27,7 @@ const BusinessHoursPage = () => { - + ); diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx index a0a268caab9..bccd437a650 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx @@ -10,6 +10,7 @@ import { useOmnichannelPriorities } from '../../../../ee/client/omnichannel/hook import { PriorityIcon } from '../../../../ee/client/omnichannel/priorities/PriorityIcon'; import GenericNoResults from '../../../components/GenericNoResults'; import { + GenericTable, GenericTableBody, GenericTableCell, GenericTableHeader, @@ -17,7 +18,6 @@ import { GenericTableLoadingTable, GenericTableRow, } from '../../../components/GenericTable'; -import { GenericTable } from '../../../components/GenericTable/V2/GenericTable'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../components/GenericTable/hooks/useSort'; import Page from '../../../components/Page'; diff --git a/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx b/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx index f666ea31019..5aa47489347 100644 --- a/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx +++ b/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx @@ -1,69 +1,43 @@ -import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import { Meteor } from 'meteor/meteor'; -import type { FC } from 'react'; -import React, { useState, useMemo, useCallback } from 'react'; +import { Pagination } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useRoute, useTranslation, useEndpoint, useUserId } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import React, { useState, useMemo } from 'react'; -import GenericTable from '../../../../components/GenericTable'; -import { useEndpointData } from '../../../../hooks/useEndpointData'; +import FilterByText from '../../../../components/FilterByText'; +import GenericNoResults from '../../../../components/GenericNoResults/GenericNoResults'; +import { + GenericTable, + GenericTableBody, + GenericTableHeader, + GenericTableHeaderCell, + GenericTableLoadingRow, +} from '../../../../components/GenericTable'; +import { usePagination } from '../../../../components/GenericTable/hooks/usePagination'; +import { useSort } from '../../../../components/GenericTable/hooks/useSort'; import { CallTableRow } from './CallTableRow'; -const useQuery = ( - { - text, - itemsPerPage, - current, - }: { - text?: string; - itemsPerPage: 25 | 50 | 100; - current: number; - }, - [column, direction]: string[], - userIdLoggedIn: string | null, -): { - sort: string; - open: 'false'; - roomName: string; - agents: string[]; - count?: number; - current?: number; -} => - useMemo( +const CallTable = () => { + const t = useTranslation(); + const userIdLoggedIn = useUserId(); + + const [text, setText] = useState(''); + const directoryRoute = useRoute('omnichannel-directory'); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); + const { sortBy, sortDirection, setSort } = useSort<'fname' | 'phone' | 'queue' | 'ts' | 'callDuration' | 'direction'>('fname'); + + const query = useMemo( () => ({ - sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }), - open: 'false', + sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, + open: 'false' as const, roomName: text || '', agents: userIdLoggedIn ? [userIdLoggedIn] : [], ...(itemsPerPage && { count: itemsPerPage }), ...(current && { offset: current }), }), - [column, current, direction, itemsPerPage, userIdLoggedIn, text], + [sortBy, current, sortDirection, itemsPerPage, userIdLoggedIn, text], ); -const CallTable: FC = () => { - const [params, setParams] = useState<{ text?: string; current: number; itemsPerPage: 25 | 50 | 100 }>({ - text: '', - current: 0, - itemsPerPage: 25, - }); - const [sort, setSort] = useState<[string, 'asc' | 'desc']>(['closedAt', 'desc']); - const t = useTranslation(); - const debouncedParams = useDebouncedValue(params, 500); - const debouncedSort = useDebouncedValue(sort, 500); - const userIdLoggedIn = Meteor.userId(); - const query = useQuery(debouncedParams, debouncedSort, userIdLoggedIn); - const directoryRoute = useRoute('omnichannel-directory'); - - const onHeaderClick = useMutableCallback((id) => { - const [sortBy, sortDirection] = sort; - - if (sortBy === id) { - setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); - return; - } - setSort([id, 'asc']); - }); - const onRowClick = useMutableCallback((id, token) => { directoryRoute.push( { @@ -75,74 +49,81 @@ const CallTable: FC = () => { ); }); - const { value: data } = useEndpointData('/v1/voip/rooms', { params: query }); + const getVoipRooms = useEndpoint('GET', '/v1/voip/rooms'); + const { data, isSuccess, isLoading } = useQuery(['voip-rooms', query], async () => getVoipRooms(query)); - const header = useMemo( - () => - [ - - {t('Contact_Name')} - , - - {t('Phone')} - , - - {t('Queue')} - , - - {t('Started_At')} - , - - {t('Talk_Time')} - , - - {t('Direction')} - , - , - ].filter(Boolean), - [sort, onHeaderClick, t], + const headers = ( + <> + + {t('Contact_Name')} + + + {t('Phone')} + + + {t('Queue')} + + + {t('Started_At')} + + + {t('Talk_Time')} + + + {t('Direction')} + + + ); - const renderRow = useCallback((room) => , [onRowClick]); - return ( - } - /> + <> + setText(text)} /> + {isLoading && ( + + {headers} + + + + + )} + {isSuccess && data?.rooms.length === 0 && } + {isSuccess && data?.rooms.length > 0 && ( + <> + + {headers} + + {data?.rooms.map((room) => ( + + ))} + + + + + )} + ); }; diff --git a/apps/meteor/client/views/omnichannel/directory/calls/CallTableRow.tsx b/apps/meteor/client/views/omnichannel/directory/calls/CallTableRow.tsx index 89d0a92013b..8ead7e104cc 100644 --- a/apps/meteor/client/views/omnichannel/directory/calls/CallTableRow.tsx +++ b/apps/meteor/client/views/omnichannel/directory/calls/CallTableRow.tsx @@ -1,4 +1,4 @@ -import type { IVoipRoom } from '@rocket.chat/core-typings'; +import type { IVoipRoom, Serialized } from '@rocket.chat/core-typings'; import { Table } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; import moment from 'moment'; @@ -10,7 +10,7 @@ import { useIsCallReady } from '../../../../contexts/CallContext'; import { CallDialpadButton } from '../components/CallDialpadButton'; type CallTableRowProps = { - room: IVoipRoom; + room: Serialized; onRowClick(_id: string, token?: string): void; }; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx index 01055e185b9..50c1651f69f 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx @@ -1,71 +1,45 @@ -import { Table, Tag, Box } from '@rocket.chat/fuselage'; -import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import { Meteor } from 'meteor/meteor'; +import { Tag, Box, Pagination, States, StatesIcon, StatesTitle, StatesActions, StatesAction } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useRoute, useTranslation, useUserId } from '@rocket.chat/ui-contexts'; import moment from 'moment'; -import type { FC, ReactElement } from 'react'; import React, { useState, useMemo, useCallback } from 'react'; import FilterByText from '../../../../components/FilterByText'; -import GenericTable, { GenericTableLoadingTable } from '../../../../components/GenericTable'; +import GenericNoResults from '../../../../components/GenericNoResults/GenericNoResults'; +import { + GenericTable, + GenericTableBody, + GenericTableCell, + GenericTableHeader, + GenericTableHeaderCell, + GenericTableLoadingTable, + GenericTableRow, +} from '../../../../components/GenericTable'; +import { usePagination } from '../../../../components/GenericTable/hooks/usePagination'; +import { useSort } from '../../../../components/GenericTable/hooks/useSort'; import { useCurrentChats } from '../../currentChats/hooks/useCurrentChats'; -const useQuery = ( - { - text, - itemsPerPage, - current, - }: { - text?: string; - itemsPerPage: 25 | 50 | 100; - current: number; - }, - [column, direction]: string[], - userIdLoggedIn: string | null, -): { - sort: string; - open: boolean; - roomName: string; - agents: string[]; - count?: number; - current?: number; -} => - useMemo( +const ChatTable = () => { + const t = useTranslation(); + const [text, setText] = useState(''); + const userIdLoggedIn = useUserId(); + const directoryRoute = useRoute('omnichannel-directory'); + + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); + const { sortBy, sortDirection, setSort } = useSort<'fname' | 'department' | 'ts' | 'chatDuration' | 'closedAt'>('fname'); + + const query = useMemo( () => ({ - sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }), + sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, open: false, roomName: text || '', agents: userIdLoggedIn ? [userIdLoggedIn] : [], ...(itemsPerPage && { count: itemsPerPage }), ...(current && { offset: current }), }), - [column, current, direction, itemsPerPage, userIdLoggedIn, text], + [sortBy, current, sortDirection, itemsPerPage, userIdLoggedIn, text], ); -const ChatTable: FC = () => { - const [params, setParams] = useState<{ text?: string; current: number; itemsPerPage: 25 | 50 | 100 }>({ - text: '', - current: 0, - itemsPerPage: 25, - }); - const [sort, setSort] = useState<[string, 'asc' | 'desc']>(['closedAt', 'desc']); - const t = useTranslation(); - const debouncedParams = useDebouncedValue(params, 500); - const debouncedSort = useDebouncedValue(sort, 500); - const userIdLoggedIn = Meteor.userId(); - const query = useQuery(debouncedParams, debouncedSort, userIdLoggedIn); - const directoryRoute = useRoute('omnichannel-directory'); - - const onHeaderClick = useMutableCallback((id) => { - const [sortBy, sortDirection] = sort; - - if (sortBy === id) { - setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); - return; - } - setSort([id, 'asc']); - }); - const onRowClick = useMutableCallback((id) => directoryRoute.push({ page: 'chats', @@ -74,60 +48,53 @@ const ChatTable: FC = () => { }), ); - const header = useMemo( - () => - [ - - {t('Contact_Name')} - , - - {t('Department')} - , - - {t('Started_At')} - , - - {t('Chat_Duration')} - , - - {t('Closed_At')} - , - ].filter(Boolean), - [sort, onHeaderClick, t], + const headers = ( + <> + + {t('Contact_Name')} + + + {t('Department')} + + + {t('Started_At')} + + + {t('Chat_Duration')} + + + {t('Closed_At')} + + ); + const { data, isLoading, isSuccess, isError, refetch } = useCurrentChats(query); + const renderRow = useCallback( ({ _id, fname, ts, closedAt, department, tags }) => ( - onRowClick(_id)} action qa-user-id={_id}> - + onRowClick(_id)} action qa-user-id={_id}> + {fname} {tags && ( @@ -151,35 +118,55 @@ const ChatTable: FC = () => { )} - - {department ? department.name : ''} - {moment(ts).format('L LTS')} - {moment(closedAt).from(moment(ts), true)} - {moment(closedAt).format('L LTS')} - + + {department ? department.name : ''} + {moment(ts).format('L LTS')} + {moment(closedAt).from(moment(ts), true)} + {moment(closedAt).format('L LTS')} + ), [onRowClick], ); - const result = useCurrentChats(query); - - if (result.isLoading) { - return ; - } - if (result.error) { - return {t('Something_went_wrong')}; - } - return ( - } - /> + <> + setText(text)} /> + {isLoading && ( + + {headers} + + + + + )} + {isSuccess && data?.rooms.length === 0 && } + {isSuccess && data?.rooms.length > 0 && ( + <> + + {headers} + {data?.rooms.map((room) => renderRow(room))} + + + + )} + {isError && ( + + + {t('Something_went_wrong')} + + refetch()}>{t('Reload_page')} + + + )} + ); }; diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/ContactTab.js b/apps/meteor/client/views/omnichannel/directory/contacts/ContactTab.tsx similarity index 82% rename from apps/meteor/client/views/omnichannel/directory/contacts/ContactTab.js rename to apps/meteor/client/views/omnichannel/directory/contacts/ContactTab.tsx index d11cdd78392..6942a33afc3 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/ContactTab.js +++ b/apps/meteor/client/views/omnichannel/directory/contacts/ContactTab.tsx @@ -4,14 +4,14 @@ import React from 'react'; import NotAuthorizedPage from '../../../notAuthorized/NotAuthorizedPage'; import ContactTable from './ContactTable'; -function ContactTab(props) { +const ContactTab = () => { const hasAccess = usePermission('view-l-room'); if (hasAccess) { - return ; + return ; } return ; -} +}; export default ContactTab; diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.tsx b/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.tsx index 1fc33d5b953..9fbde9f866b 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.tsx +++ b/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.tsx @@ -13,7 +13,7 @@ import { GenericTableBody, GenericTableRow, GenericTableHeaderCell, - GenericTableLoadingTable, + GenericTableLoadingRow, } from '../../../../components/GenericTable'; import { usePagination } from '../../../../components/GenericTable/hooks/usePagination'; import { useSort } from '../../../../components/GenericTable/hooks/useSort'; @@ -63,7 +63,35 @@ function ContactTable(): ReactElement { }), ); - const { data: contactResult, isLoading, isError, isSuccess, refetch } = useCurrentContacts(query); + const { data, isLoading, isError, isSuccess, refetch } = useCurrentContacts(query); + + const headers = ( + <> + + {t('Username')} + + + {t('Name')} + + + {t('Phone')} + + + {t('Email')} + + + {t('Last_Chat')} + + + + ); + return ( <> setTerm(text)} /> + {isLoading && ( + + {headers} + + + + + )} + {isSuccess && data?.visitors.length > 0 && ( + <> + + {headers} + + {data?.visitors.map(({ _id, username, fname, name, visitorEmails, phone, lastChat }) => { + const phoneNumber = (phone?.length && phone[0].phoneNumber) || ''; + const visitorEmail = visitorEmails?.length && visitorEmails[0].address; - - - - {t('Username')} - - - {t('Name')} - - - {t('Phone')} - - - {t('Email')} - - - {t('Last_Chat')} - - - - - {contactResult && - contactResult.visitors.map(({ _id, username, fname, name, visitorEmails, phone, lastChat }) => { - const phoneNumber = (phone?.length && phone[0].phoneNumber) || ''; - const visitorEmail = visitorEmails?.length && visitorEmails[0].address; - - return ( - - {username} - {parseOutboundPhoneNumber(fname || name)} - {parseOutboundPhoneNumber(phoneNumber)} - {visitorEmail} - {lastChat && formatDate(lastChat.ts)} - {isCallReady && } - - ); - })} - {isLoading && } - {isError && {t('Something_went_wrong')}} - - - + return ( + + {username} + {parseOutboundPhoneNumber(fname || name)} + {parseOutboundPhoneNumber(phoneNumber)} + {visitorEmail} + {lastChat && formatDate(lastChat.ts)} + {isCallReady && } + + ); + })} + + + + + )} {isError && ( @@ -155,16 +163,6 @@ function ContactTable(): ReactElement { )} - {isSuccess && ( - - )} ); } diff --git a/apps/meteor/client/views/omnichannel/queueList/QueueListPage.tsx b/apps/meteor/client/views/omnichannel/queueList/QueueListPage.tsx index 53e5bdfb812..315b7afe9a4 100644 --- a/apps/meteor/client/views/omnichannel/queueList/QueueListPage.tsx +++ b/apps/meteor/client/views/omnichannel/queueList/QueueListPage.tsx @@ -1,49 +1,20 @@ -import type { Dispatch, Key, ReactElement, ReactNode, SetStateAction } from 'react'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; -import GenericTable from '../../../components/GenericTable'; import Page from '../../../components/Page'; -import { QueueListFilter } from './QueueListFilter'; +import QueueListTable from './QueueListTable'; -type QueueListPagePropsParamsType = { - servedBy: string; - status: string; - departmentId: string; - itemsPerPage: 25 | 50 | 100; - current: number; -}; +const QueueListPage = () => { + const t = useTranslation(); -type QueueListPagePropsType = { - title: string; - header: ReactNode; - data?: { - queue: { - chats: number; - department: { _id: string; name: string }; - user: { _id: string; username: string; status: string }; - }[]; - count: number; - offset: number; - total: number; - }; - params: QueueListPagePropsParamsType; - setParams: Dispatch>; - renderRow: (props: { _id?: Key }) => ReactElement; + return ( + + + + + + + ); }; -export const QueueListPage = ({ title, header, data, renderRow, params, setParams }: QueueListPagePropsType): ReactElement => ( - - - - } - renderRow={renderRow} - results={data?.queue} - total={data?.total} - params={params} - setParams={setParams as (params: Pick) => void} - /> - - -); +export default QueueListPage; diff --git a/apps/meteor/client/views/omnichannel/queueList/QueueListTable.tsx b/apps/meteor/client/views/omnichannel/queueList/QueueListTable.tsx new file mode 100644 index 00000000000..c75393fb7f3 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/queueList/QueueListTable.tsx @@ -0,0 +1,145 @@ +import { Box, Pagination } from '@rocket.chat/fuselage'; +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import type { ReactElement } from 'react'; +import React, { useMemo, useState } from 'react'; + +import GenericNoResults from '../../../components/GenericNoResults'; +import { + GenericTable, + GenericTableHeader, + GenericTableHeaderCell, + GenericTableBody, + GenericTableRow, + GenericTableCell, + GenericTableLoadingRow, +} from '../../../components/GenericTable'; +import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; +import { useSort } from '../../../components/GenericTable/hooks/useSort'; +import UserAvatar from '../../../components/avatar/UserAvatar'; +import { QueueListFilter } from './QueueListFilter'; + +const QueueListTable = (): ReactElement => { + const t = useTranslation(); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); + const { sortBy, sortDirection, setSort } = useSort<'servedBy' | 'department' | 'total' | 'status'>('servedBy'); + + const [filters, setFilters] = useState<{ + servedBy: string; + status: string; + departmentId: string; + }>({ + servedBy: '', + status: '', + departmentId: '', + }); + + const mediaQuery = useMediaQuery('(min-width: 1024px)'); + + const headers = ( + <> + {mediaQuery && ( + + {t('Served_By')} + + )} + + {t('Department')} + + + {t('Total')} + + + {t('Status')} + + + ); + + const query = useMemo(() => { + const query: { + agentId?: string; + includeOfflineAgents?: 'true' | 'false'; + departmentId?: string; + sort: string; + count: number; + } = { + sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, + ...(itemsPerPage && { count: itemsPerPage }), + ...(current && { offset: current }), + }; + + if (filters.status !== 'online') { + query.includeOfflineAgents = 'true'; + } + if (filters.servedBy) { + query.agentId = filters.servedBy; + } + if (filters.departmentId) { + query.departmentId = filters.departmentId; + } + + return query; + }, [sortBy, sortDirection, itemsPerPage, current, filters.status, filters.departmentId, filters.servedBy]); + + const getLivechatQueue = useEndpoint('GET', '/v1/livechat/queue'); + const { data, isSuccess, isLoading } = useQuery(['livechat-queue', query], async () => getLivechatQueue(query), { + refetchOnWindowFocus: false, + }); + + return ( + <> + + {isLoading && ( + + {headers} + + + + + )} + {isSuccess && data?.queue.length === 0 && } + {isSuccess && data?.queue.length > 0 && ( + <> + + {headers} + + {data?.queue.map(({ user, department, chats }) => ( + + + + + + {user.username} + + + + {department ? department.name : ''} + {chats} + {user.status === 'online' ? t('Online') : t('Offline')} + + ))} + + + + + )} + + ); +}; + +export default QueueListTable; diff --git a/apps/meteor/client/views/omnichannel/queueList/index.ts b/apps/meteor/client/views/omnichannel/queueList/index.ts new file mode 100644 index 00000000000..0622dd44ee8 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/queueList/index.ts @@ -0,0 +1 @@ +export { default } from './QueueListPage'; diff --git a/apps/meteor/client/views/omnichannel/queueList/index.tsx b/apps/meteor/client/views/omnichannel/queueList/index.tsx deleted file mode 100644 index 856aa659641..00000000000 --- a/apps/meteor/client/views/omnichannel/queueList/index.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { Box, Table } from '@rocket.chat/fuselage'; -import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React, { useCallback, useMemo, useState } from 'react'; - -import GenericTable from '../../../components/GenericTable'; -import UserAvatar from '../../../components/avatar/UserAvatar'; -import { useEndpointData } from '../../../hooks/useEndpointData'; -import { QueueListPage } from './QueueListPage'; -import { useQuery } from './hooks/useQuery'; - -const QueueList = (): ReactElement => { - const t = useTranslation(); - const [sort, setSort] = useState<[string, 'asc' | 'desc']>(['servedBy', 'desc']); - - const onHeaderClick = useMutableCallback((id) => { - const [sortBy, sortDirection] = sort; - - if (sortBy === id) { - setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); - return; - } - setSort([id, 'asc']); - }); - - const mediaQuery = useMediaQuery('(min-width: 1024px)'); - - const header = useMemo( - () => - [ - mediaQuery && ( - - {t('Served_By')} - - ), - - {t('Department')} - , - - {t('Total')} - , - - {t('Status')} - , - ].filter(Boolean), - [mediaQuery, sort, onHeaderClick, t], - ); - - const renderRow = useCallback( - ({ user, department, chats }) => { - const getStatusText = (): string => { - if (user.status === 'online') { - return t('Online'); - } - - return t('Offline'); - }; - - return ( - - - - - - {user.username} - - - - {department ? department.name : ''} - {chats} - {getStatusText()} - - ); - }, - [mediaQuery, t], - ); - - const [params, setParams] = useState<{ - servedBy: string; - status: string; - departmentId: string; - itemsPerPage: 25 | 50 | 100; - current: number; - }>({ - servedBy: '', - status: '', - departmentId: '', - itemsPerPage: 25, - current: 0, - }); - const debouncedParams = useDebouncedValue(params, 500); - const debouncedSort = useDebouncedValue(sort, 500); - const query = useQuery(debouncedParams, debouncedSort); - const { value: data } = useEndpointData('/v1/livechat/queue', { params: query }); - - return ( - - ); -}; - -export default QueueList; diff --git a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.js b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.js deleted file mode 100644 index c6dfe58619a..00000000000 --- a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.js +++ /dev/null @@ -1,81 +0,0 @@ -import { Box, CheckBox } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useState, useCallback } from 'react'; - -import GenericTable from '../../../../../components/GenericTable'; -import ChannelRow from './ChannelRow'; - -const ChannelDeletionTable = ({ rooms, params, onChangeParams, onChangeRoomSelection, selectedRooms, onToggleAllRooms }) => { - const [sort, setSort] = useState(['name', 'asc']); - - const t = useTranslation(); - - const selectedRoomsLength = Object.values(selectedRooms).filter(Boolean).length; - - const onHeaderClick = useCallback( - (id) => { - const [sortBy, sortDirection] = sort; - if (sortBy === id) { - setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); - return; - } - setSort([id, 'asc']); - }, - [sort], - ); - - const getSortedChannels = () => { - if (rooms) { - const sortedRooms = [...rooms]; - const [sortBy, sortOrder] = sort; - if (sortBy === 'name') { - sortedRooms.sort((a, b) => (a.name && b.name ? a.name.localeCompare(b.name) : 0)); - } - if (sortBy === 'usersCount') { - sortedRooms.sort((a, b) => a.usersCount - b.usersCount); - } - if (sortOrder === 'desc') { - return sortedRooms?.reverse(); - } - return sortedRooms; - } - }; - - const checked = rooms.length === selectedRoomsLength; - const indeterminate = rooms.length > selectedRoomsLength && selectedRoomsLength > 0; - - return ( - - - - - {t('Channel_name')} - - - - {t('Members')} - - - - } - results={getSortedChannels()} - params={params} - setParams={onChangeParams} - fixed={false} - pagination={false} - > - {({ key, ...room }) => } - - - ); -}; - -export default ChannelDeletionTable; diff --git a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx new file mode 100644 index 00000000000..751c5848efe --- /dev/null +++ b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTable.tsx @@ -0,0 +1,78 @@ +import type { IRoom, Serialized } from '@rocket.chat/core-typings'; +import { Box, CheckBox } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { GenericTable, GenericTableHeaderCell, GenericTableBody, GenericTableHeader } from '../../../../../components/GenericTable'; +import { useSort } from '../../../../../components/GenericTable/hooks/useSort'; +import ChannelDeletionTableRow from './ChannelDeletionTableRow'; + +type ChannelDeletationTable = { + rooms: Serialized[]; + onToggleAllRooms: () => void; + onChangeRoomSelection: (room: Serialized) => void; + selectedRooms: { [key: string]: Serialized }; +}; + +const ChannelDeletionTable = ({ rooms, onChangeRoomSelection, selectedRooms, onToggleAllRooms }: ChannelDeletationTable) => { + const t = useTranslation(); + const { sortBy, sortDirection, setSort } = useSort<'name' | 'usersCount'>('name'); + + const selectedRoomsLength = Object.values(selectedRooms).filter(Boolean).length; + + const getSortedChannels = () => { + if (rooms) { + const sortedRooms = [...rooms]; + if (sortBy === 'name') { + sortedRooms.sort((a, b) => (a.name && b.name ? a.name.localeCompare(b.name) : 0)); + } + if (sortBy === 'usersCount') { + sortedRooms.sort((a, b) => a.usersCount - b.usersCount); + } + if (sortDirection === 'desc') { + return sortedRooms?.reverse(); + } + return sortedRooms; + } + }; + + const sortedRooms = getSortedChannels(); + + const checked = rooms.length === selectedRoomsLength; + const indeterminate = rooms.length > selectedRoomsLength && selectedRoomsLength > 0; + + const headers = ( + <> + + + {t('Channel_name')} + + + + {t('Members')} + + + + ); + + return ( + + + {headers} + + {sortedRooms?.map((room) => ( + + ))} + + + + ); +}; + +export default ChannelDeletionTable; diff --git a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx new file mode 100644 index 00000000000..f14d73725e6 --- /dev/null +++ b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelDeletionTableRow.tsx @@ -0,0 +1,35 @@ +import type { IRoom, Serialized } from '@rocket.chat/core-typings'; +import { CheckBox, Margins } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import React from 'react'; + +import { GenericTableRow, GenericTableCell } from '../../../../../components/GenericTable'; +import { RoomIcon } from '../../../../../components/RoomIcon'; + +type ChannelDeletionTableRowProps = { + room: Serialized; + onChange: (room: Serialized) => void; + selected: boolean; +}; + +const ChannelDeletionTableRow = ({ room, onChange, selected }: ChannelDeletionTableRowProps) => { + const { name, fname, usersCount } = room; + const handleChange = useMutableCallback(() => onChange(room)); + + return ( + + + + + + {fname ?? name} + + + + {usersCount} + + + ); +}; + +export default ChannelDeletionTableRow; diff --git a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelRow.js b/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelRow.js deleted file mode 100644 index 8bd71154149..00000000000 --- a/apps/meteor/client/views/teams/contextualBar/info/Delete/ChannelRow.js +++ /dev/null @@ -1,29 +0,0 @@ -import { CheckBox, Table, Icon, Margins } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import React from 'react'; - -import { useRoomIcon } from '../../../../../hooks/useRoomIcon'; - -const ChannelRow = ({ onChange, selected, room }) => { - const { name, fname, usersCount } = room; - - const handleChange = useMutableCallback(() => onChange(room)); - - return ( - - - - - - {fname ?? name} - - - - - {usersCount} - - - ); -}; - -export default ChannelRow; diff --git a/apps/meteor/ee/client/omnichannel/BusinessHoursRow.js b/apps/meteor/ee/client/omnichannel/BusinessHoursRow.js index bcbe1dd6ad2..005c3aa464c 100644 --- a/apps/meteor/ee/client/omnichannel/BusinessHoursRow.js +++ b/apps/meteor/ee/client/omnichannel/BusinessHoursRow.js @@ -1,7 +1,7 @@ -import { Table } from '@rocket.chat/fuselage'; import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, useMemo } from 'react'; +import { GenericTableRow, GenericTableCell } from '../../../client/components/GenericTable'; import RemoveBusinessHourButton from './RemoveBusinessHourButton'; function BusinessHoursRow(props) { @@ -43,17 +43,17 @@ function BusinessHoursRow(props) { }; return ( - - {name || t('Default')} - {t(timezone.name)} - {openDays.join(', ')} - {active ? t('Yes') : t('No')} + + {name || t('Default')} + {t(timezone.name)} + {openDays.join(', ')} + {active ? t('Yes') : t('No')} {name && ( - + - + )} - + ); } diff --git a/apps/meteor/ee/client/omnichannel/BusinessHoursTable.js b/apps/meteor/ee/client/omnichannel/BusinessHoursTable.js index e243b7ab9bc..3d2c0a27f16 100644 --- a/apps/meteor/ee/client/omnichannel/BusinessHoursTable.js +++ b/apps/meteor/ee/client/omnichannel/BusinessHoursTable.js @@ -1,37 +1,92 @@ -import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import { Pagination, States, StatesIcon, StatesActions, StatesAction, StatesTitle } from '@rocket.chat/fuselage'; +import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import React, { useMemo, useState } from 'react'; import FilterByText from '../../../client/components/FilterByText'; -import GenericTable from '../../../client/components/GenericTable'; -import { useResizeInlineBreakpoint } from '../../../client/hooks/useResizeInlineBreakpoint'; +import GenericNoResults from '../../../client/components/GenericNoResults'; +import { + GenericTable, + GenericTableBody, + GenericTableHeaderCell, + GenericTableHeader, + GenericTableLoadingRow, +} from '../../../client/components/GenericTable'; +import { usePagination } from '../../../client/components/GenericTable/hooks/usePagination'; import BusinessHoursRow from './BusinessHoursRow'; -function BusinessHoursTable({ businessHours, totalbusinessHours, params, onChangeParams, reload }) { +const BusinessHoursTable = () => { const t = useTranslation(); + const [text, setText] = useState(''); - const [ref, onMediumBreakpoint] = useResizeInlineBreakpoint([600], 200); + const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); + + const query = useMemo( + () => ({ + count: itemsPerPage, + offset: current, + name: text, + }), + [itemsPerPage, current, text], + ); + + const getBusinessHours = useEndpoint('GET', '/v1/livechat/business-hours'); + const { data, isLoading, isSuccess, isError, refetch } = useQuery(['livechat-buiness-hours', query], async () => getBusinessHours(query)); + + const headers = ( + <> + {t('Name')} + {t('Timezone')} + {t('Open_Days')} + {t('Enabled')} + {t('Remove')} + + ); return ( - + setText(text)} /> + {isLoading && ( + + {headers} + + + + + )} + {isSuccess && data?.businessHours.length === 0 && } + {isSuccess && data?.businessHours.length > 0 && ( <> - {t('Name')} - {t('Timezone')} - {t('Open_Days')} - {t('Enabled')} - {t('Remove')} + + {headers} + + {data?.businessHours.map((businessHour) => ( + + ))} + + + - } - results={businessHours} - total={totalbusinessHours} - params={params} - setParams={onChangeParams} - renderFilter={({ onChange, ...props }) => } - > - {(props) => } - + )} + {isError && ( + + + {t('Something_went_wrong')} + + refetch()}>{t('Reload_page')} + + + )} + ); -} +}; export default BusinessHoursTable; diff --git a/apps/meteor/ee/client/omnichannel/BusinessHoursTable.stories.tsx b/apps/meteor/ee/client/omnichannel/BusinessHoursTable.stories.tsx index 09c720fddd2..3e1a8d0371c 100644 --- a/apps/meteor/ee/client/omnichannel/BusinessHoursTable.stories.tsx +++ b/apps/meteor/ee/client/omnichannel/BusinessHoursTable.stories.tsx @@ -14,7 +14,7 @@ export default { }, } as ComponentMeta; -export const Default: ComponentStory = (args) => ; +export const Default: ComponentStory = (_args) => ; Default.storyName = 'BusinessHoursTable'; Default.args = { businessHours: [ diff --git a/apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js b/apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js deleted file mode 100644 index 34fa91217e6..00000000000 --- a/apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Callout } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useMemo, useState } from 'react'; - -import { AsyncStatePhase } from '../../../client/hooks/useAsyncState'; -import { useEndpointData } from '../../../client/hooks/useEndpointData'; -import BusinessHoursTable from './BusinessHoursTable'; - -const BusinessHoursTableContainer = () => { - const t = useTranslation(); - const [params, setParams] = useState(() => ({ current: 0, itemsPerPage: 25, text: '' })); - - const { - value: data, - phase: state, - reload, - } = useEndpointData('/v1/livechat/business-hours', { - params: useMemo( - () => ({ - count: params.itemsPerPage, - offset: params.current, - name: params.text, - }), - [params], - ), - }); - - if (state === AsyncStatePhase.REJECTED) { - return {t('Error')}: error; - } - - return ( - - ); -}; - -export default BusinessHoursTableContainer; diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesPage.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesPage.tsx index e69dd74d1a4..6a44a799e40 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesPage.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesPage.tsx @@ -1,49 +1,41 @@ import { Button, ButtonGroup } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import type { FC, ReactElement, Dispatch, SetStateAction } from 'react'; +import { useRoute, useRouteParameter, useTranslation } from '@rocket.chat/ui-contexts'; +import { useQueryClient } from '@tanstack/react-query'; import React from 'react'; -import GenericNoResults from '../../../../client/components/GenericNoResults'; -import GenericTable from '../../../../client/components/GenericTable'; import Page from '../../../../client/components/Page'; +import CannedResponseEditWithData from './CannedResponseEditWithData'; +import CannedResponseNew from './CannedResponseNew'; +import CannedResponsesTable from './CannedResponsesTable'; -type CannedResponsesPageProps = { - data: any; - header: ReactElement[]; - setParams: Dispatch>; - params: { current: number; itemsPerPage: 25 | 50 | 100 }; - title: string; - renderFilter?: (props: any) => ReactElement; - renderRow?: (props: any) => ReactElement; - totalCannedResponses: number; - busy?: boolean; -}; - -const CannedResponsesPage: FC = ({ - data, - header, - setParams, - params, - title, - renderRow, - renderFilter, - totalCannedResponses, - busy, -}) => { +const CannedResponsesPage = () => { const t = useTranslation(); - - const Route = useRoute('omnichannel-canned-responses'); + const cannedResponseRoute = useRoute('omnichannel-canned-responses'); + const queryClient = useQueryClient(); const handleClick = useMutableCallback(() => - Route.push({ + cannedResponseRoute.push({ context: 'new', }), ); + const context = useRouteParameter('context'); + const id = useRouteParameter('id'); + + const reload = useMutableCallback(() => queryClient.invalidateQueries(['canned-responses'])); + + if (context === 'edit' && id) { + return ; + } + + if (context === 'new') { + return ; + } + return ( - +