mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 06:47:25 +00:00
[FIX] Reload roomslist after successful deletion of a room from admin panel. (#23795)
* fix- delete rooms from admin refresh roomlist and close contextual menu * added success toast message * fix-lint * fix-lint: missing dependency * Handle failing `eraseRoom` call * Handle list changes on save/archive too * Handle teams as a special case Co-authored-by: Tasso Evangelista <tasso.evangelista@rocket.chat>
This commit is contained in:
parent
a14a305cc7
commit
a25766dd7d
@ -1,4 +1,4 @@
|
||||
import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, Callout, TextAreaInput } from '@rocket.chat/fuselage';
|
||||
import { Box, Button, ButtonGroup, TextInput, Field, ToggleSwitch, Icon, TextAreaInput } from '@rocket.chat/fuselage';
|
||||
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
|
||||
@ -8,11 +8,14 @@ import VerticalBar from '../../../components/VerticalBar';
|
||||
import RoomAvatarEditor from '../../../components/avatar/RoomAvatarEditor';
|
||||
import { usePermission } from '../../../contexts/AuthorizationContext';
|
||||
import { useSetModal } from '../../../contexts/ModalContext';
|
||||
import { useMethod } from '../../../contexts/ServerContext';
|
||||
import { useRoute } from '../../../contexts/RouterContext';
|
||||
import { useEndpoint, useMethod } from '../../../contexts/ServerContext';
|
||||
import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext';
|
||||
import { useTranslation } from '../../../contexts/TranslationContext';
|
||||
import { useEndpointActionExperimental } from '../../../hooks/useEndpointActionExperimental';
|
||||
import { useForm } from '../../../hooks/useForm';
|
||||
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
|
||||
import DeleteTeamModal from '../../teams/contextualBar/info/Delete/DeleteTeamModal';
|
||||
|
||||
const getInitialValues = (room) => ({
|
||||
roomName: room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, { type: room.t, ...room }),
|
||||
@ -28,13 +31,13 @@ const getInitialValues = (room) => ({
|
||||
roomAvatar: undefined,
|
||||
});
|
||||
|
||||
function EditRoom({ room, onChange }) {
|
||||
function EditRoom({ room, onChange, onDelete }) {
|
||||
const t = useTranslation();
|
||||
|
||||
const [deleted, setDeleted] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
|
||||
const setModal = useSetModal();
|
||||
|
||||
const dispatchToastMessage = useToastMessageDispatch();
|
||||
const { values, handlers, hasUnsavedChanges, reset } = useForm(getInitialValues(room));
|
||||
|
||||
const [canViewName, canViewTopic, canViewAnnouncement, canViewArchived, canViewDescription, canViewType, canViewReadOnly] =
|
||||
@ -81,6 +84,8 @@ function EditRoom({ room, onChange }) {
|
||||
|
||||
const changeArchivation = archived !== !!room.archived;
|
||||
|
||||
const roomsRoute = useRoute('admin-rooms');
|
||||
|
||||
const canDelete = usePermission(`delete-${room.t}`);
|
||||
|
||||
const archiveSelector = room.archived ? 'unarchive' : 'archive';
|
||||
@ -115,18 +120,57 @@ function EditRoom({ room, onChange }) {
|
||||
handleRoomType(roomType === 'p' ? 'c' : 'p');
|
||||
});
|
||||
|
||||
const deleteRoom = useMethod('eraseRoom');
|
||||
const eraseRoom = useMethod('eraseRoom');
|
||||
const deleteTeam = useEndpoint('POST', 'teams.delete');
|
||||
|
||||
const handleDelete = useMutableCallback(() => {
|
||||
const onCancel = () => setModal(undefined);
|
||||
const onConfirm = async () => {
|
||||
await deleteRoom(room._id);
|
||||
onCancel();
|
||||
setDeleted(true);
|
||||
};
|
||||
if (room.teamMain) {
|
||||
setModal(
|
||||
<DeleteTeamModal
|
||||
onConfirm={async (deletedRooms) => {
|
||||
const roomsToRemove = Array.isArray(deletedRooms) && deletedRooms.length > 0 ? deletedRooms : [];
|
||||
|
||||
try {
|
||||
setDeleting(true);
|
||||
setModal(null);
|
||||
await deleteTeam({ teamId: room.teamId, ...(roomsToRemove.length && { roomsToRemove }) });
|
||||
dispatchToastMessage({ type: 'success', message: t('Team_has_been_deleted') });
|
||||
roomsRoute.push({});
|
||||
} catch (error) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
setDeleting(false);
|
||||
} finally {
|
||||
onDelete();
|
||||
}
|
||||
}}
|
||||
onCancel={() => setModal(null)}
|
||||
teamId={room.teamId}
|
||||
/>,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setModal(
|
||||
<GenericModal variant='danger' onConfirm={onConfirm} onCancel={onCancel} confirmText={t('Yes_delete_it')}>
|
||||
<GenericModal
|
||||
variant='danger'
|
||||
onConfirm={async () => {
|
||||
try {
|
||||
setDeleting(true);
|
||||
setModal(null);
|
||||
await eraseRoom(room._id);
|
||||
dispatchToastMessage({ type: 'success', message: t('Room_has_been_deleted') });
|
||||
roomsRoute.push({});
|
||||
} catch (error) {
|
||||
dispatchToastMessage({ type: 'error', message: error });
|
||||
setDeleting(false);
|
||||
} finally {
|
||||
onDelete();
|
||||
}
|
||||
}}
|
||||
onCancel={() => setModal(null)}
|
||||
confirmText={t('Yes_delete_it')}
|
||||
>
|
||||
{t('Delete_Room_Warning')}
|
||||
</GenericModal>,
|
||||
);
|
||||
@ -134,7 +178,6 @@ function EditRoom({ room, onChange }) {
|
||||
|
||||
return (
|
||||
<VerticalBar.ScrollableContent is='form' onSubmit={useMutableCallback((e) => e.preventDefault())}>
|
||||
{deleted && <Callout type='danger' title={t('Room_has_been_deleted')}></Callout>}
|
||||
{room.t !== 'd' && (
|
||||
<Box pbe='x24' display='flex' justifyContent='center'>
|
||||
<RoomAvatarEditor roomAvatar={roomAvatar} room={room} onChangeAvatar={handleRoomAvatar} />
|
||||
@ -143,7 +186,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field>
|
||||
<Field.Label>{t('Name')}</Field.Label>
|
||||
<Field.Row>
|
||||
<TextInput disabled={deleted || !canViewName} value={roomName} onChange={handleRoomName} flexGrow={1} />
|
||||
<TextInput disabled={deleting || !canViewName} value={roomName} onChange={handleRoomName} flexGrow={1} />
|
||||
</Field.Row>
|
||||
</Field>
|
||||
{room.t !== 'd' && (
|
||||
@ -158,7 +201,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field>
|
||||
<Field.Label>{t('Description')}</Field.Label>
|
||||
<Field.Row>
|
||||
<TextAreaInput rows={4} disabled={deleted} value={roomDescription} onChange={handleRoomDescription} flexGrow={1} />
|
||||
<TextAreaInput rows={4} disabled={deleting} value={roomDescription} onChange={handleRoomDescription} flexGrow={1} />
|
||||
</Field.Row>
|
||||
</Field>
|
||||
)}
|
||||
@ -166,7 +209,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field>
|
||||
<Field.Label>{t('Announcement')}</Field.Label>
|
||||
<Field.Row>
|
||||
<TextAreaInput rows={4} disabled={deleted} value={roomAnnouncement} onChange={handleRoomAnnouncement} flexGrow={1} />
|
||||
<TextAreaInput rows={4} disabled={deleting} value={roomAnnouncement} onChange={handleRoomAnnouncement} flexGrow={1} />
|
||||
</Field.Row>
|
||||
</Field>
|
||||
)}
|
||||
@ -174,7 +217,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field>
|
||||
<Field.Label>{t('Topic')}</Field.Label>
|
||||
<Field.Row>
|
||||
<TextAreaInput rows={4} disabled={deleted} value={roomTopic} onChange={handleRoomTopic} flexGrow={1} />
|
||||
<TextAreaInput rows={4} disabled={deleting} value={roomTopic} onChange={handleRoomTopic} flexGrow={1} />
|
||||
</Field.Row>
|
||||
</Field>
|
||||
)}
|
||||
@ -182,7 +225,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field>
|
||||
<Field.Row>
|
||||
<Field.Label>{t('Private')}</Field.Label>
|
||||
<ToggleSwitch disabled={deleted} checked={roomType === 'p'} onChange={changeRoomType} />
|
||||
<ToggleSwitch disabled={deleting} checked={roomType === 'p'} onChange={changeRoomType} />
|
||||
</Field.Row>
|
||||
<Field.Hint>{t('Just_invited_people_can_access_this_channel')}</Field.Hint>
|
||||
</Field>
|
||||
@ -192,7 +235,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field.Row>
|
||||
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
|
||||
<Field.Label>{t('Read_only')}</Field.Label>
|
||||
<ToggleSwitch disabled={deleted} checked={readOnly} onChange={handleReadOnly} />
|
||||
<ToggleSwitch disabled={deleting} checked={readOnly} onChange={handleReadOnly} />
|
||||
</Box>
|
||||
</Field.Row>
|
||||
<Field.Hint>{t('Only_authorized_users_can_write_new_messages')}</Field.Hint>
|
||||
@ -203,7 +246,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field.Row>
|
||||
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
|
||||
<Field.Label>{t('Room_archivation_state_true')}</Field.Label>
|
||||
<ToggleSwitch disabled={deleted} checked={archived} onChange={handleArchived} />
|
||||
<ToggleSwitch disabled={deleting} checked={archived} onChange={handleArchived} />
|
||||
</Box>
|
||||
</Field.Row>
|
||||
</Field>
|
||||
@ -214,7 +257,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field.Row>
|
||||
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
|
||||
<Field.Label>{t('Default')}</Field.Label>
|
||||
<ToggleSwitch disabled={deleted} checked={isDefault} onChange={handleIsDefault} />
|
||||
<ToggleSwitch disabled={deleting} checked={isDefault} onChange={handleIsDefault} />
|
||||
</Box>
|
||||
</Field.Row>
|
||||
</Field>
|
||||
@ -222,7 +265,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field.Row>
|
||||
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
|
||||
<Field.Label>{t('Favorite')}</Field.Label>
|
||||
<ToggleSwitch disabled={deleted} checked={favorite} onChange={handleFavorite} />
|
||||
<ToggleSwitch disabled={deleting} checked={favorite} onChange={handleFavorite} />
|
||||
</Box>
|
||||
</Field.Row>
|
||||
</Field>
|
||||
@ -230,7 +273,7 @@ function EditRoom({ room, onChange }) {
|
||||
<Field.Row>
|
||||
<Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}>
|
||||
<Field.Label>{t('Featured')}</Field.Label>
|
||||
<ToggleSwitch disabled={deleted} checked={featured} onChange={handleFeatured} />
|
||||
<ToggleSwitch disabled={deleting} checked={featured} onChange={handleFeatured} />
|
||||
</Box>
|
||||
</Field.Row>
|
||||
</Field>
|
||||
@ -238,10 +281,10 @@ function EditRoom({ room, onChange }) {
|
||||
<Field.Row>
|
||||
<Box display='flex' flexDirection='row' justifyContent='space-between' w='full'>
|
||||
<ButtonGroup stretch flexGrow={1}>
|
||||
<Button type='reset' disabled={!hasUnsavedChanges || deleted} onClick={reset}>
|
||||
<Button type='reset' disabled={!hasUnsavedChanges || deleting} onClick={reset}>
|
||||
{t('Reset')}
|
||||
</Button>
|
||||
<Button flexGrow={1} disabled={!hasUnsavedChanges || deleted} onClick={handleSave}>
|
||||
<Button flexGrow={1} disabled={!hasUnsavedChanges || deleting} onClick={handleSave}>
|
||||
{t('Save')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
@ -250,7 +293,7 @@ function EditRoom({ room, onChange }) {
|
||||
</Field>
|
||||
<Field>
|
||||
<Field.Row>
|
||||
<Button primary flexGrow={1} danger disabled={deleted || !canDelete} onClick={handleDelete}>
|
||||
<Button primary flexGrow={1} danger disabled={deleting || !canDelete} onClick={handleDelete}>
|
||||
<Icon name='trash' size='x16' />
|
||||
{t('Delete')}
|
||||
</Button>
|
||||
|
||||
@ -4,9 +4,9 @@ import NotAuthorizedPage from '../../../components/NotAuthorizedPage';
|
||||
import { usePermission } from '../../../contexts/AuthorizationContext';
|
||||
import EditRoomWithData from './EditRoomWithData';
|
||||
|
||||
function EditRoomContextBar({ rid }) {
|
||||
function EditRoomContextBar({ rid, onReload }) {
|
||||
const canViewRoomAdministration = usePermission('view-room-administration');
|
||||
return canViewRoomAdministration ? <EditRoomWithData rid={rid} /> : <NotAuthorizedPage />;
|
||||
return canViewRoomAdministration ? <EditRoomWithData rid={rid} onReload={onReload} /> : <NotAuthorizedPage />;
|
||||
}
|
||||
|
||||
export default EditRoomContextBar;
|
||||
|
||||
@ -5,7 +5,7 @@ import { AsyncStatePhase } from '../../../hooks/useAsyncState';
|
||||
import { useEndpointData } from '../../../hooks/useEndpointData';
|
||||
import EditRoom from './EditRoom';
|
||||
|
||||
function EditRoomWithData({ rid }) {
|
||||
function EditRoomWithData({ rid, onReload }) {
|
||||
const {
|
||||
value: data = {},
|
||||
phase: state,
|
||||
@ -33,7 +33,16 @@ function EditRoomWithData({ rid }) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return <EditRoom room={{ type: data.t, ...data }} onChange={reload} />;
|
||||
const handleChange = () => {
|
||||
reload();
|
||||
onReload();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
onReload();
|
||||
};
|
||||
|
||||
return <EditRoom room={{ type: data.t, ...data }} onChange={handleChange} onDelete={handleDelete} />;
|
||||
}
|
||||
|
||||
export default EditRoomWithData;
|
||||
|
||||
@ -1,12 +1,28 @@
|
||||
import React from 'react';
|
||||
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
|
||||
import Page from '../../../components/Page';
|
||||
import VerticalBar from '../../../components/VerticalBar';
|
||||
import { useRouteParameter, useRoute } from '../../../contexts/RouterContext';
|
||||
import { useTranslation } from '../../../contexts/TranslationContext';
|
||||
import { useEndpointData } from '../../../hooks/useEndpointData';
|
||||
import EditRoomContextBar from './EditRoomContextBar';
|
||||
import RoomsTable from './RoomsTable';
|
||||
|
||||
export const DEFAULT_TYPES = ['d', 'p', 'c', 'teams'];
|
||||
|
||||
const useQuery = ({ text, types, itemsPerPage, current }, [column, direction]) =>
|
||||
useMemo(
|
||||
() => ({
|
||||
filter: text || '',
|
||||
types,
|
||||
sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }),
|
||||
...(itemsPerPage && { count: itemsPerPage }),
|
||||
...(current && { offset: current }),
|
||||
}),
|
||||
[text, types, itemsPerPage, current, column, direction],
|
||||
);
|
||||
|
||||
export function RoomsPage() {
|
||||
const t = useTranslation();
|
||||
|
||||
@ -19,12 +35,27 @@ export function RoomsPage() {
|
||||
roomsRoute.push({});
|
||||
};
|
||||
|
||||
const [params, setParams] = useState({
|
||||
text: '',
|
||||
types: DEFAULT_TYPES,
|
||||
current: 0,
|
||||
itemsPerPage: 25,
|
||||
});
|
||||
const [sort, setSort] = useState(['name', 'asc']);
|
||||
|
||||
const debouncedParams = useDebouncedValue(params, 500);
|
||||
const debouncedSort = useDebouncedValue(sort, 500);
|
||||
|
||||
const query = useQuery(debouncedParams, debouncedSort);
|
||||
|
||||
const endpointData = useEndpointData('rooms.adminRooms', query);
|
||||
|
||||
return (
|
||||
<Page flexDirection='row'>
|
||||
<Page>
|
||||
<Page.Header title={t('Rooms')} />
|
||||
<Page.Content>
|
||||
<RoomsTable />
|
||||
<RoomsTable endpointData={endpointData} params={params} onChangeParams={setParams} sort={sort} onChangeSort={setSort} />
|
||||
</Page.Content>
|
||||
</Page>
|
||||
{context && (
|
||||
@ -34,7 +65,7 @@ export function RoomsPage() {
|
||||
<VerticalBar.Close onClick={handleVerticalBarCloseButtonClick} />
|
||||
</VerticalBar.Header>
|
||||
|
||||
<EditRoomContextBar rid={id} />
|
||||
<EditRoomContextBar rid={id} onReload={endpointData.reload} />
|
||||
</VerticalBar>
|
||||
)}
|
||||
</Page>
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
import { Box, Table, Icon } from '@rocket.chat/fuselage';
|
||||
import { useMediaQuery, useDebouncedValue } from '@rocket.chat/fuselage-hooks';
|
||||
import React, { useMemo, useCallback, useState } from 'react';
|
||||
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
|
||||
import GenericTable from '../../../components/GenericTable';
|
||||
import RoomAvatar from '../../../components/avatar/RoomAvatar';
|
||||
import { useRoute } from '../../../contexts/RouterContext';
|
||||
import { useTranslation } from '../../../contexts/TranslationContext';
|
||||
import { useEndpointData } from '../../../hooks/useEndpointData';
|
||||
import { AsyncStatePhase } from '../../../lib/asyncState';
|
||||
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
|
||||
import FilterByTypeAndText from './FilterByTypeAndText';
|
||||
|
||||
const style = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' };
|
||||
|
||||
export const DEFAULT_TYPES = ['d', 'p', 'c', 'teams'];
|
||||
|
||||
export const roomTypeI18nMap = {
|
||||
l: 'Omnichannel',
|
||||
c: 'Channel',
|
||||
@ -30,18 +27,6 @@ const getRoomType = (room) => {
|
||||
return roomTypeI18nMap[room.t];
|
||||
};
|
||||
|
||||
const useQuery = ({ text, types, itemsPerPage, current }, [column, direction]) =>
|
||||
useMemo(
|
||||
() => ({
|
||||
filter: text || '',
|
||||
types,
|
||||
sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }),
|
||||
...(itemsPerPage && { count: itemsPerPage }),
|
||||
...(current && { offset: current }),
|
||||
}),
|
||||
[text, types, itemsPerPage, current, column, direction],
|
||||
);
|
||||
|
||||
const getRoomDisplayName = (room) => (room.t === 'd' ? room.usernames.join(' x ') : roomCoordinator.getRoomName(room.t, room));
|
||||
|
||||
const useDisplayData = (asyncState, sort) =>
|
||||
@ -66,29 +51,14 @@ const useDisplayData = (asyncState, sort) =>
|
||||
return value.rooms;
|
||||
}, [asyncState, sort]);
|
||||
|
||||
function RoomsTable() {
|
||||
function RoomsTable({ endpointData, params, onChangeParams, sort, onChangeSort }) {
|
||||
const t = useTranslation();
|
||||
|
||||
const mediaQuery = useMediaQuery('(min-width: 1024px)');
|
||||
|
||||
const [params, setParams] = useState({
|
||||
text: '',
|
||||
types: DEFAULT_TYPES,
|
||||
current: 0,
|
||||
itemsPerPage: 25,
|
||||
});
|
||||
const [sort, setSort] = useState(['name', 'asc']);
|
||||
|
||||
const routeName = 'admin-rooms';
|
||||
|
||||
const debouncedParams = useDebouncedValue(params, 500);
|
||||
const debouncedSort = useDebouncedValue(sort, 500);
|
||||
|
||||
const query = useQuery(debouncedParams, debouncedSort);
|
||||
|
||||
const asyncState = useEndpointData('rooms.adminRooms', query);
|
||||
|
||||
const { value: data = {} } = asyncState;
|
||||
const { value: data = {} } = endpointData;
|
||||
|
||||
const router = useRoute(routeName);
|
||||
|
||||
@ -106,15 +76,15 @@ function RoomsTable() {
|
||||
const [sortBy, sortDirection] = sort;
|
||||
|
||||
if (sortBy === id) {
|
||||
setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']);
|
||||
onChangeSort([id, sortDirection === 'asc' ? 'desc' : 'asc']);
|
||||
return;
|
||||
}
|
||||
setSort([id, 'asc']);
|
||||
onChangeSort([id, 'asc']);
|
||||
},
|
||||
[sort],
|
||||
[sort, onChangeSort],
|
||||
);
|
||||
|
||||
const displayData = useDisplayData(asyncState, sort);
|
||||
const displayData = useDisplayData(endpointData, sort);
|
||||
|
||||
const header = useMemo(
|
||||
() =>
|
||||
@ -218,7 +188,7 @@ function RoomsTable() {
|
||||
renderRow={renderRow}
|
||||
results={displayData}
|
||||
total={data.total}
|
||||
setParams={setParams}
|
||||
setParams={onChangeParams}
|
||||
params={params}
|
||||
renderFilter={({ onChange, ...props }) => <FilterByTypeAndText setFilter={onChange} {...props} />}
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user