Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/meteor/client/components/GenericModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ type GenericModalProps = RequiredModalProps & {
title?: string | ReactElement;
icon?: ComponentProps<typeof Icon>['name'] | ReactElement | null;
confirmDisabled?: boolean;
onCancel?: () => void;
onClose: () => void;
onCancel: () => void;
onClose?: () => void;
onConfirm: () => void;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Button, ButtonGroup, TextInput, Field, Select } from '@rocket.chat/fuselage';
import React, { useCallback, useState } from 'react';
import { Button, ButtonGroup, TextInput, Field, Select, SelectOption } from '@rocket.chat/fuselage';
import React, { ReactElement, SyntheticEvent, useCallback, useState } from 'react';

import VerticalBar from '../../../components/VerticalBar';
import { useMethod } from '../../../contexts/ServerContext';
import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext';
import { useTranslation } from '../../../contexts/TranslationContext';

function AddCustomUserStatus({ goToNew, close, onChange, ...props }) {
type AddCustomUserStatus = {
goToNew: (result: any) => () => void;
close: () => void;
onChange: () => void;
};

function AddCustomUserStatus({ goToNew, close, onChange, ...props }: AddCustomUserStatus): ReactElement {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();

Expand All @@ -27,11 +33,11 @@ function AddCustomUserStatus({ goToNew, close, onChange, ...props }) {
goToNew(result)();
onChange();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
dispatchToastMessage({ type: 'error', message: String(error) });
}
}, [dispatchToastMessage, goToNew, name, onChange, saveStatus, statusType, t]);

const presenceOptions = [
const presenceOptions: SelectOption[] = [
['online', t('Online')],
['busy', t('Busy')],
['away', t('Away')],
Expand All @@ -43,7 +49,7 @@ function AddCustomUserStatus({ goToNew, close, onChange, ...props }) {
<Field>
<Field.Label>{t('Name')}</Field.Label>
<Field.Row>
<TextInput value={name} onChange={(e) => setName(e.currentTarget.value)} placeholder={t('Name')} />
<TextInput value={name} onChange={(e: SyntheticEvent<HTMLInputElement>) => setName(e.currentTarget.value)} placeholder={t('Name')} />
</Field.Row>
</Field>
<Field>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,49 @@
import { Table } from '@rocket.chat/fuselage';
import React, { useMemo } from 'react';
import React, { CSSProperties, KeyboardEventHandler, MouseEventHandler, ReactElement, useMemo } from 'react';

import FilterByText from '../../../components/FilterByText';
import GenericTable from '../../../components/GenericTable';
import MarkdownText from '../../../components/MarkdownText';
import { useTranslation } from '../../../contexts/TranslationContext';
import type { PaginatedResult } from '/definition/rest/helpers/PaginatedResult';

const style = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' };
const style: CSSProperties = { whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' };

function CustomUserStatus({ data, sort, onClick, onHeaderClick, setParams, params }) {
type StatusType = {
_id: string;
name: string;
statusType: string | null;
};

type ParamsType = {
text?: string;
current?: number;
itemsPerPage?: 25 | 50 | 100;
};

type CustomUserStatusProps = {
data: PaginatedResult<{
statuses: {
_id: string;
name: string;
statusType: string | null;
}[];
}>;
sort: [string, 'asc' | 'desc'];
onClick: (_id: string, stauts: StatusType) => KeyboardEventHandler<HTMLElement> | MouseEventHandler<HTMLElement>;
onHeaderClick: (((sort: string) => void) & React.MouseEventHandler<HTMLElement>) | undefined;
setParams: (params: ParamsType) => void;
params: ParamsType;
};

const CustomUserStatus = function CustomUserStatus({
data,
sort,
onClick,
onHeaderClick,
setParams,
params,
}: CustomUserStatusProps): ReactElement {
const t = useTranslation();

const header = useMemo(
Expand All @@ -30,10 +65,18 @@ function CustomUserStatus({ data, sort, onClick, onHeaderClick, setParams, param
[onHeaderClick, sort, t],
);

const renderRow = (status) => {
const renderRow = (status: StatusType) => {
const { _id, name, statusType } = status;
return (
<Table.Row key={_id} onKeyDown={onClick(_id, status)} onClick={onClick(_id, status)} tabIndex={0} role='link' action qa-user-id={_id}>
<Table.Row
key={_id}
onKeyDown={onClick(_id, status) as KeyboardEventHandler<HTMLElement>}
onClick={onClick(_id, status) as MouseEventHandler<HTMLElement>}
tabIndex={0}
role='link'
action
qa-user-id={_id}
>
<Table.Cell fontScale='p2' color='default' style={style}>
<MarkdownText content={name} parseEmoji={true} variant='inline' />
</Table.Cell>
Expand All @@ -52,9 +95,9 @@ function CustomUserStatus({ data, sort, onClick, onHeaderClick, setParams, param
total={data?.total ?? 0}
setParams={setParams}
params={params}
renderFilter={({ onChange, ...props }) => <FilterByText onChange={onChange} {...props} />}
renderFilter={({ onChange, ...props }) => <FilterByText onChange={onChange as (filter: { text: string }) => void} {...props} />}
/>
);
}
};

export default CustomUserStatus;
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Button, Icon } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import React, { useMemo, useState, useCallback } from 'react';

import Page from '../../../components/Page';
import VerticalBar from '../../../components/VerticalBar';
import { usePermission } from '../../../contexts/AuthorizationContext';
import { useRoute, useRouteParameter } from '../../../contexts/RouterContext';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useEndpointData } from '../../../hooks/useEndpointData';
import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage';
import AddCustomUserStatus from './AddCustomUserStatus';
import CustomUserStatus from './CustomUserStatus';
import EditCustomUserStatusWithData from './EditCustomUserStatusWithData';

function CustomUserStatusRoute() {
const route = useRoute('custom-user-status');
const context = useRouteParameter('context');
const id = useRouteParameter('id');
const canManageUserStatus = usePermission('manage-user-status');

const t = useTranslation();

const [params, setParams] = useState(() => ({ text: '', current: 0, itemsPerPage: 25 }));
const [sort, setSort] = useState(() => ['name', 'asc']);

const { text, itemsPerPage, current } = useDebouncedValue(params, 500);
const [column, direction] = useDebouncedValue(sort, 500);
const query = useMemo(
() => ({
query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }),
sort: JSON.stringify({ [column]: direction === 'asc' ? 1 : -1 }),
...(itemsPerPage && { count: itemsPerPage }),
...(current && { offset: current }),
}),
[text, itemsPerPage, current, column, direction],
);

const { value: data, reload } = useEndpointData('custom-user-status.list', query);

const handleItemClick = (_id) => () => {
route.push({
context: 'edit',
id: _id,
});
};

const handleHeaderClick = (id) => {
setSort(([sortBy, sortDirection]) => {
if (sortBy === id) {
return [id, sortDirection === 'asc' ? 'desc' : 'asc'];
}

return [id, 'asc'];
});
};

const handleNewButtonClick = useCallback(() => {
route.push({ context: 'new' });
}, [route]);

const handleClose = useCallback(() => {
route.push({});
}, [route]);

const handleChange = useCallback(() => {
reload();
}, [reload]);

if (!canManageUserStatus) {
return <NotAuthorizedPage />;
}

return (
<Page flexDirection='row'>
<Page name='admin-custom-user-status'>
<Page.Header title={t('Custom_User_Status')}>
<Button primary onClick={handleNewButtonClick} aria-label={t('New')}>
<Icon name='plus' /> {t('New')}
</Button>
</Page.Header>
<Page.Content>
<CustomUserStatus
setParams={setParams}
params={params}
onHeaderClick={handleHeaderClick}
data={data}
onClick={handleItemClick}
sort={sort}
/>
</Page.Content>
</Page>
{context && (
<VerticalBar flexShrink={0}>
<VerticalBar.Header>
{context === 'edit' && t('Custom_User_Status_Edit')}
{context === 'new' && t('Custom_User_Status_Add')}
<VerticalBar.Close onClick={handleClose} />
</VerticalBar.Header>

{context === 'edit' && <EditCustomUserStatusWithData _id={id} close={handleClose} onChange={handleChange} />}
{context === 'new' && <AddCustomUserStatus goToNew={handleItemClick} close={handleClose} onChange={handleChange} />}
</VerticalBar>
)}
</Page>
);
}

export default CustomUserStatusRoute;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, ButtonGroup, TextInput, Field, Select, Icon } from '@rocket.chat/fuselage';
import React, { useCallback, useState, useMemo, useEffect } from 'react';
import { Button, ButtonGroup, TextInput, Field, Select, Icon, SelectOption } from '@rocket.chat/fuselage';
import React, { useCallback, useState, useMemo, useEffect, SyntheticEvent } from 'react';

import GenericModal from '../../../components/GenericModal';
import VerticalBar from '../../../components/VerticalBar';
Expand All @@ -8,7 +8,16 @@ import { useMethod } from '../../../contexts/ServerContext';
import { useToastMessageDispatch } from '../../../contexts/ToastMessagesContext';
import { useTranslation } from '../../../contexts/TranslationContext';

export function EditCustomUserStatus({ close, onChange, data, ...props }) {
type EditCustomUserStatusProps = {
close: () => void;
onChange: () => void;
data: {
_id: string;
name: string;
statusType: string | null;
};
};
export function EditCustomUserStatus({ close, onChange, data, ...props }: EditCustomUserStatusProps) {
const t = useTranslation();
const dispatchToastMessage = useToastMessageDispatch();
const setModal = useSetModal();
Expand Down Expand Up @@ -45,7 +54,7 @@ export function EditCustomUserStatus({ close, onChange, data, ...props }) {
});
onChange();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
dispatchToastMessage({ type: 'error', message: String(error) });
}
}, [saveStatus, _id, previousName, previousStatusType, name, statusType, dispatchToastMessage, t, onChange]);

Expand All @@ -60,12 +69,12 @@ export function EditCustomUserStatus({ close, onChange, data, ...props }) {
try {
await deleteStatus(_id);
setModal(() => (
<GenericModal variant='success' onClose={handleClose} onConfirm={handleClose}>
<GenericModal variant='success' onCancel={handleClose} onConfirm={handleClose}>
{t('Custom_User_Status_Has_Been_Deleted')}
</GenericModal>
));
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
dispatchToastMessage({ type: 'error', message: String(error) });
onChange();
}
};
Expand All @@ -81,7 +90,7 @@ export function EditCustomUserStatus({ close, onChange, data, ...props }) {
));
}, [_id, close, deleteStatus, dispatchToastMessage, onChange, setModal, t]);

const presenceOptions = [
const presenceOptions: SelectOption[] = [
['online', t('Online')],
['busy', t('Busy')],
['away', t('Away')],
Expand All @@ -93,7 +102,7 @@ export function EditCustomUserStatus({ close, onChange, data, ...props }) {
<Field>
<Field.Label>{t('Name')}</Field.Label>
<Field.Row>
<TextInput value={name} onChange={(e) => setName(e.currentTarget.value)} placeholder={t('Name')} />
<TextInput value={name} onChange={(e: SyntheticEvent<HTMLInputElement>) => setName(e.currentTarget.value)} placeholder={t('Name')} />
</Field.Row>
</Field>
<Field>
Expand Down
13 changes: 10 additions & 3 deletions packages/rest-typings/src/v1/customUserStatus.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { PaginatedRequest } from '../helpers/PaginatedRequest';
import type { PaginatedResult } from '../helpers/PaginatedResult';

export type CustomUserStatusEndpoints = {
'custom-user-status.list': {
GET: (params: { query: string }) => {
statuses: unknown[];
};
GET: (params: PaginatedRequest<{ query: string }>) => PaginatedResult<{
statuses: {
_id: string;
name: string;
statusType: string | null;
}[];
}>;
};
};