feat: ability to edit user settings in bulk (#597)

This commit is contained in:
David
2021-01-24 16:29:43 -08:00
committed by GitHub
parent 2f75c4c6ae
commit 4b0241c3b3
8 changed files with 492 additions and 289 deletions

View File

@@ -0,0 +1,115 @@
import React, { useEffect, useState } from 'react';
import PermissionEdit from '../PermissionEdit';
import Modal from '../Common/Modal';
import { User, useUser } from '../../hooks/useUser';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import axios from 'axios';
import { useToasts } from 'react-toast-notifications';
import { messages as userEditMessages } from '../UserEdit';
interface BulkEditProps {
selectedUserIds: number[];
users?: User[];
onCancel?: () => void;
onComplete?: (updatedUsers: User[]) => void;
onSaving?: (isSaving: boolean) => void;
}
const messages = defineMessages({
userssaved: 'Users saved',
});
const BulkEditModal: React.FC<BulkEditProps> = ({
selectedUserIds,
users,
onCancel,
onComplete,
onSaving,
}) => {
const { user: currentUser } = useUser();
const intl = useIntl();
const { addToast } = useToasts();
const [currentPermission, setCurrentPermission] = useState(0);
const [isSaving, setIsSaving] = useState(false);
useEffect(() => {
if (onSaving) {
onSaving(isSaving);
}
}, [isSaving, onSaving]);
const updateUsers = async () => {
try {
setIsSaving(true);
const { data: updated } = await axios.put<User[]>(`/api/v1/user`, {
ids: selectedUserIds,
permissions: currentPermission,
});
if (onComplete) {
onComplete(updated);
}
addToast(intl.formatMessage(messages.userssaved), {
appearance: 'success',
autoDismiss: true,
});
} catch (e) {
addToast(intl.formatMessage(userEditMessages.userfail), {
appearance: 'error',
autoDismiss: true,
});
} finally {
setIsSaving(false);
}
};
useEffect(() => {
if (users) {
const selectedUsers = users.filter((u) => selectedUserIds.includes(u.id));
const { permissions: allPermissionsEqual } = selectedUsers.reduce(
({ permissions: aPerms }, { permissions: bPerms }) => {
return {
permissions: aPerms === bPerms ? aPerms : NaN,
};
},
{ permissions: selectedUsers[0].permissions }
);
if (allPermissionsEqual) {
setCurrentPermission(allPermissionsEqual);
}
}
}, [users, selectedUserIds]);
return (
<Modal
title={intl.formatMessage(userEditMessages.edituser)}
onOk={() => {
updateUsers();
}}
okDisabled={isSaving}
okText={intl.formatMessage(userEditMessages.save)}
onCancel={onCancel}
>
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base font-medium leading-6 sm:text-sm sm:leading-5"
id="label-permissions"
>
<FormattedMessage {...userEditMessages.permissions} />
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<PermissionEdit
user={currentUser}
currentPermission={currentPermission}
onUpdate={(newPermission) => setCurrentPermission(newPermission)}
/>
</div>
</div>
</div>
</Modal>
);
};
export default BulkEditModal;

View File

@@ -6,7 +6,7 @@ import Badge from '../Common/Badge';
import { FormattedDate, defineMessages, useIntl } from 'react-intl';
import Button from '../Common/Button';
import { hasPermission } from '../../../server/lib/permissions';
import { Permission, UserType } from '../../hooks/useUser';
import { Permission, UserType, useUser } from '../../hooks/useUser';
import { useRouter } from 'next/router';
import Header from '../Common/Header';
import Table from '../Common/Table';
@@ -19,6 +19,7 @@ import { Field, Form, Formik } from 'formik';
import * as Yup from 'yup';
import AddUserIcon from '../../assets/useradd.svg';
import Alert from '../Common/Alert';
import BulkEditModal from './BulkEditModal';
const messages = defineMessages({
userlist: 'User List',
@@ -33,6 +34,7 @@ const messages = defineMessages({
created: 'Created',
lastupdated: 'Last Updated',
edit: 'Edit',
bulkedit: 'Bulk Edit',
delete: 'Delete',
admin: 'Admin',
user: 'User',
@@ -78,6 +80,39 @@ const UserList: React.FC = () => {
}>({
isOpen: false,
});
const [showBulkEditModal, setShowBulkEditModal] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<number[]>([]);
const { user: currentUser } = useUser();
const isUserPermsEditable = (userId: number) =>
userId !== 1 && userId !== currentUser?.id;
const isAllUsersSelected = () => {
return (
selectedUsers.length ===
data?.filter((user) => user.id !== currentUser?.id).length
);
};
const isUserSelected = (userId: number) => selectedUsers.includes(userId);
const toggleAllUsers = () => {
if (
data &&
selectedUsers.length >= 0 &&
selectedUsers.length < data?.length - 1
) {
setSelectedUsers(
data.filter((user) => isUserPermsEditable(user.id)).map((u) => u.id)
);
} else {
setSelectedUsers([]);
}
};
const toggleUser = (userId: number) => {
if (selectedUsers.includes(userId)) {
setSelectedUsers((users) => users.filter((u) => u !== userId));
} else {
setSelectedUsers((users) => [...users, userId]);
}
};
const deleteUser = async () => {
setDeleting(true);
@@ -183,6 +218,7 @@ const UserList: React.FC = () => {
{intl.formatMessage(messages.deleteconfirm)}
</Modal>
</Transition>
<Transition
enter="opacity-0 transition duration-300"
enterFrom="opacity-0"
@@ -313,6 +349,27 @@ const UserList: React.FC = () => {
}}
</Formik>
</Transition>
<Transition
enter="opacity-0 transition duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={showBulkEditModal}
>
<BulkEditModal
onCancel={() => setShowBulkEditModal(false)}
onComplete={() => {
setShowBulkEditModal(false);
revalidate();
}}
selectedUserIds={selectedUsers}
users={data}
/>
</Transition>
<div className="flex flex-col justify-between sm:flex-row">
<Header>{intl.formatMessage(messages.userlist)}</Header>
<div className="flex">
@@ -333,21 +390,57 @@ const UserList: React.FC = () => {
</Button>
</div>
</div>
<Table>
<thead>
<tr>
<Table.TH>
<input
type="checkbox"
id="selectAll"
name="selectAll"
checked={isAllUsersSelected()}
onChange={() => {
toggleAllUsers();
}}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</Table.TH>
<Table.TH>{intl.formatMessage(messages.username)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.totalrequests)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.usertype)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.role)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.created)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.lastupdated)}</Table.TH>
<Table.TH></Table.TH>
<Table.TH className="text-right">
<Button
buttonSize="sm"
buttonType="warning"
onClick={() => setShowBulkEditModal(true)}
disabled={selectedUsers.length === 0}
>
{intl.formatMessage(messages.bulkedit)}
</Button>
</Table.TH>
</tr>
</thead>
<Table.TBody>
{data?.map((user) => (
<tr key={`user-list-${user.id}`}>
<Table.TD>
{isUserPermsEditable(user.id) && (
<input
type="checkbox"
id={`user-list-select-${user.id}`}
name={`user-list-select-${user.id}`}
checked={isUserSelected(user.id)}
onChange={() => {
toggleUser(user.id);
}}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
)}
</Table.TD>
<Table.TD>
<div className="flex items-center">
<div className="flex-shrink-0 w-10 h-10">