commit 43d49a980e1391af3d68945a118b488b731f5b0c
Author: Jaroslav Hrubý <hrubyj@students.zcu.cz>
Date:   Mon Apr 25 14:46:17 2022 +0200

    Modal windows for user edit, delete and assigned documents were implemented

diff --git a/webapp/api/api.ts b/webapp/api/api.ts
index ce1f3d8..e4725da 100644
--- a/webapp/api/api.ts
+++ b/webapp/api/api.ts
@@ -186,6 +186,43 @@ export interface AnnotationsAddRequest {
      */
     documentIdList?: Array<string> | null;
 }
+/**
+ *
+ * @export
+ * @interface ChangeUserInfoRequest
+ */
+export interface ChangeUserInfoRequest {
+    /**
+     *
+     * @type {string}
+     * @memberof ChangeUserInfoRequest
+     */
+    password?: string | null;
+    /**
+     *
+     * @type {string}
+     * @memberof ChangeUserInfoRequest
+     */
+    username?: string | null;
+    /**
+     *
+     * @type {string}
+     * @memberof ChangeUserInfoRequest
+     */
+    name?: string | null;
+    /**
+     *
+     * @type {string}
+     * @memberof ChangeUserInfoRequest
+     */
+    surname?: string | null;
+    /**
+     *
+     * @type {ERole}
+     * @memberof ChangeUserInfoRequest
+     */
+    role?: ERole;
+}
 /**
  *
  * @export
@@ -211,6 +248,43 @@ export interface ClientInfo {
      */
     ip?: string | null;
 }
+/**
+ *
+ * @export
+ * @interface CreateUserRequest
+ */
+export interface CreateUserRequest {
+    /**
+     *
+     * @type {string}
+     * @memberof CreateUserRequest
+     */
+    username?: string | null;
+    /**
+     *
+     * @type {string}
+     * @memberof CreateUserRequest
+     */
+    password?: string | null;
+    /**
+     *
+     * @type {string}
+     * @memberof CreateUserRequest
+     */
+    name?: string | null;
+    /**
+     *
+     * @type {string}
+     * @memberof CreateUserRequest
+     */
+    surname?: string | null;
+    /**
+     *
+     * @type {ERole}
+     * @memberof CreateUserRequest
+     */
+    role?: ERole;
+}
 /**
  *
  * @export
@@ -255,7 +329,6 @@ export interface DocumentAddRequest {
  * @interface DocumentListInfo
  */
 export interface DocumentListInfo {
-    key?: string;
     /**
      *
      * @type {string}
@@ -702,7 +775,6 @@ export interface User {
  * @interface UserInfo
  */
 export interface UserInfo {
-    key?: string;
     /**
      *
      * @type {string}
@@ -1884,6 +1956,142 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
                 options: localVarRequestOptions,
             };
         },
+        /**
+         *
+         * @param {string} userId
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        userUserIdAnnotationsGet: async (
+            userId: string,
+            options: AxiosRequestConfig = {}
+        ): Promise<RequestArgs> => {
+            // verify required parameter 'userId' is not null or undefined
+            assertParamExists('userUserIdAnnotationsGet', 'userId', userId);
+            const localVarPath = `/user/{userId}/annotations`.replace(
+                `{${'userId'}}`,
+                encodeURIComponent(String(userId))
+            );
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options };
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions =
+                baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {
+                ...localVarHeaderParameter,
+                ...headersFromBaseOptions,
+                ...options.headers,
+            };
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         *
+         * @param {string} userId
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        userUserIdDelete: async (
+            userId: string,
+            options: AxiosRequestConfig = {}
+        ): Promise<RequestArgs> => {
+            // verify required parameter 'userId' is not null or undefined
+            assertParamExists('userUserIdDelete', 'userId', userId);
+            const localVarPath = `/user/{userId}`.replace(
+                `{${'userId'}}`,
+                encodeURIComponent(String(userId))
+            );
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = {
+                method: 'DELETE',
+                ...baseOptions,
+                ...options,
+            };
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions =
+                baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {
+                ...localVarHeaderParameter,
+                ...headersFromBaseOptions,
+                ...options.headers,
+            };
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         *
+         * @param {string} userId
+         * @param {ChangeUserInfoRequest} [changeUserInfoRequest]
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        userUserIdPut: async (
+            userId: string,
+            changeUserInfoRequest?: ChangeUserInfoRequest,
+            options: AxiosRequestConfig = {}
+        ): Promise<RequestArgs> => {
+            // verify required parameter 'userId' is not null or undefined
+            assertParamExists('userUserIdPut', 'userId', userId);
+            const localVarPath = `/user/{userId}`.replace(
+                `{${'userId'}}`,
+                encodeURIComponent(String(userId))
+            );
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options };
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions =
+                baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {
+                ...localVarHeaderParameter,
+                ...headersFromBaseOptions,
+                ...options.headers,
+            };
+            localVarRequestOptions.data = serializeDataIfNeeded(
+                changeUserInfoRequest,
+                localVarRequestOptions,
+                configuration
+            );
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
         /**
          *
          * @param {*} [options] Override http request option.
@@ -1911,6 +2119,49 @@ export const UserApiAxiosParamCreator = function (configuration?: Configuration)
                 ...options.headers,
             };
 
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         *
+         * @param {CreateUserRequest} [createUserRequest]
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        usersPost: async (
+            createUserRequest?: CreateUserRequest,
+            options: AxiosRequestConfig = {}
+        ): Promise<RequestArgs> => {
+            const localVarPath = `/users`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options };
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions =
+                baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {
+                ...localVarHeaderParameter,
+                ...headersFromBaseOptions,
+                ...options.headers,
+            };
+            localVarRequestOptions.data = serializeDataIfNeeded(
+                createUserRequest,
+                localVarRequestOptions,
+                configuration
+            );
+
             return {
                 url: toPathString(localVarUrlObj),
                 options: localVarRequestOptions,
@@ -1949,6 +2200,75 @@ export const UserApiFp = function (configuration?: Configuration) {
                 configuration
             );
         },
+        /**
+         *
+         * @param {string} userId
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async userUserIdAnnotationsGet(
+            userId: string,
+            options?: AxiosRequestConfig
+        ): Promise<
+            (
+                axios?: AxiosInstance,
+                basePath?: string
+            ) => AxiosPromise<AnnotationListResponse>
+        > {
+            const localVarAxiosArgs =
+                await localVarAxiosParamCreator.userUserIdAnnotationsGet(userId, options);
+            return createRequestFunction(
+                localVarAxiosArgs,
+                globalAxios,
+                BASE_PATH,
+                configuration
+            );
+        },
+        /**
+         *
+         * @param {string} userId
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async userUserIdDelete(
+            userId: string,
+            options?: AxiosRequestConfig
+        ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.userUserIdDelete(
+                userId,
+                options
+            );
+            return createRequestFunction(
+                localVarAxiosArgs,
+                globalAxios,
+                BASE_PATH,
+                configuration
+            );
+        },
+        /**
+         *
+         * @param {string} userId
+         * @param {ChangeUserInfoRequest} [changeUserInfoRequest]
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async userUserIdPut(
+            userId: string,
+            changeUserInfoRequest?: ChangeUserInfoRequest,
+            options?: AxiosRequestConfig
+        ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.userUserIdPut(
+                userId,
+                changeUserInfoRequest,
+                options
+            );
+            return createRequestFunction(
+                localVarAxiosArgs,
+                globalAxios,
+                BASE_PATH,
+                configuration
+            );
+        },
         /**
          *
          * @param {*} [options] Override http request option.
@@ -1965,6 +2285,27 @@ export const UserApiFp = function (configuration?: Configuration) {
                 configuration
             );
         },
+        /**
+         *
+         * @param {CreateUserRequest} [createUserRequest]
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async usersPost(
+            createUserRequest?: CreateUserRequest,
+            options?: AxiosRequestConfig
+        ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.usersPost(
+                createUserRequest,
+                options
+            );
+            return createRequestFunction(
+                localVarAxiosArgs,
+                globalAxios,
+                BASE_PATH,
+                configuration
+            );
+        },
     };
 };
 
@@ -1989,6 +2330,47 @@ export const UserApiFactory = function (
                 .userAnnotationsGet(options)
                 .then((request) => request(axios, basePath));
         },
+        /**
+         *
+         * @param {string} userId
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        userUserIdAnnotationsGet(
+            userId: string,
+            options?: any
+        ): AxiosPromise<AnnotationListResponse> {
+            return localVarFp
+                .userUserIdAnnotationsGet(userId, options)
+                .then((request) => request(axios, basePath));
+        },
+        /**
+         *
+         * @param {string} userId
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        userUserIdDelete(userId: string, options?: any): AxiosPromise<void> {
+            return localVarFp
+                .userUserIdDelete(userId, options)
+                .then((request) => request(axios, basePath));
+        },
+        /**
+         *
+         * @param {string} userId
+         * @param {ChangeUserInfoRequest} [changeUserInfoRequest]
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        userUserIdPut(
+            userId: string,
+            changeUserInfoRequest?: ChangeUserInfoRequest,
+            options?: any
+        ): AxiosPromise<void> {
+            return localVarFp
+                .userUserIdPut(userId, changeUserInfoRequest, options)
+                .then((request) => request(axios, basePath));
+        },
         /**
          *
          * @param {*} [options] Override http request option.
@@ -1999,6 +2381,20 @@ export const UserApiFactory = function (
                 .usersGet(options)
                 .then((request) => request(axios, basePath));
         },
+        /**
+         *
+         * @param {CreateUserRequest} [createUserRequest]
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        usersPost(
+            createUserRequest?: CreateUserRequest,
+            options?: any
+        ): AxiosPromise<void> {
+            return localVarFp
+                .usersPost(createUserRequest, options)
+                .then((request) => request(axios, basePath));
+        },
     };
 };
 
@@ -2021,6 +2417,50 @@ export class UserApi extends BaseAPI {
             .then((request) => request(this.axios, this.basePath));
     }
 
+    /**
+     *
+     * @param {string} userId
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public userUserIdAnnotationsGet(userId: string, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration)
+            .userUserIdAnnotationsGet(userId, options)
+            .then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     *
+     * @param {string} userId
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public userUserIdDelete(userId: string, options?: AxiosRequestConfig) {
+        return UserApiFp(this.configuration)
+            .userUserIdDelete(userId, options)
+            .then((request) => request(this.axios, this.basePath));
+    }
+
+    /**
+     *
+     * @param {string} userId
+     * @param {ChangeUserInfoRequest} [changeUserInfoRequest]
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public userUserIdPut(
+        userId: string,
+        changeUserInfoRequest?: ChangeUserInfoRequest,
+        options?: AxiosRequestConfig
+    ) {
+        return UserApiFp(this.configuration)
+            .userUserIdPut(userId, changeUserInfoRequest, options)
+            .then((request) => request(this.axios, this.basePath));
+    }
+
     /**
      *
      * @param {*} [options] Override http request option.
@@ -2032,4 +2472,20 @@ export class UserApi extends BaseAPI {
             .usersGet(options)
             .then((request) => request(this.axios, this.basePath));
     }
+
+    /**
+     *
+     * @param {CreateUserRequest} [createUserRequest]
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof UserApi
+     */
+    public usersPost(
+        createUserRequest?: CreateUserRequest,
+        options?: AxiosRequestConfig
+    ) {
+        return UserApiFp(this.configuration)
+            .usersPost(createUserRequest, options)
+            .then((request) => request(this.axios, this.basePath));
+    }
 }
diff --git a/webapp/components/modals/AddUserModal.tsx b/webapp/components/modals/AddUserModal.tsx
index 4c77b45..8a608f2 100644
--- a/webapp/components/modals/AddUserModal.tsx
+++ b/webapp/components/modals/AddUserModal.tsx
@@ -100,7 +100,7 @@ const AddUserModal: React.FC<ModalProps> = ({ onCancel }) => {
                     name="password"
                     rules={[{ required: true, message: 'Prosím zadejte heslo' }]}
                 >
-                    <Input placeholder="Heslo" />
+                    <Input.Password placeholder="Heslo" />
                 </Form.Item>
 
                 <Form.Item
diff --git a/webapp/components/modals/AssignDocumentModal.tsx b/webapp/components/modals/AssignDocumentModal.tsx
index 2de5cef..6e7bcab 100644
--- a/webapp/components/modals/AssignDocumentModal.tsx
+++ b/webapp/components/modals/AssignDocumentModal.tsx
@@ -34,10 +34,11 @@ function AssignDocumentModal({ onCancel, documentsIds }: ModalProps) {
         }
     }, [documentsIds, redirecting, role]);
 
-    const handleUpload = () => {
+    const handleUpload = async () => {
         // console.log(documentsIds, selectedUsers);
         if (documentsIds && documentsIds.length !== 0) {
-            annotationController.annotationsPost({
+            await annotationController.annotationsPost({
+                // @ts-ignore
                 documentIdList: documentsIds,
                 userIdList: selectedUsers,
             });
diff --git a/webapp/components/modals/EditUserModal.tsx b/webapp/components/modals/EditUserModal.tsx
new file mode 100644
index 0000000..94b224b
--- /dev/null
+++ b/webapp/components/modals/EditUserModal.tsx
@@ -0,0 +1,134 @@
+import { Button, Form, Input, Modal, Radio } from 'antd';
+import 'antd/dist/antd.css';
+import React from 'react';
+import { ChangeUserInfoRequest, ERole, UserInfo } from '../../api';
+import { userController } from '../../controllers';
+import { ShowToast } from '../../utils/alerts';
+
+interface ModalProps {
+    onCancel: () => void;
+    userInfo: UserInfo;
+}
+
+/**
+ * Creates a modal window that loads documents to the app.
+ * @returns The modal window.
+ */
+const EditUserModal: React.FC<ModalProps> = ({ onCancel, userInfo }: ModalProps) => {
+    /**
+     * Handles successful closing of the modal window.
+     */
+    const handleOk = () => {
+        onCancel();
+    };
+
+    /**
+     * Handles cancelling of the model window.
+     */
+    const handleCancel = () => {
+        onCancel();
+    };
+
+    const onFinish = async (values: any) => {
+        const req: ChangeUserInfoRequest = {
+            username: values.username,
+            password: values.password,
+            name: values.name,
+            surname: values.surname,
+            role: values.role,
+        };
+        if (userInfo.id) {
+            await userController.userUserIdPut(userInfo.id, req);
+        }
+        onCancel();
+    };
+
+    const onFinishFailed = (errorInfo: any) => {
+        ShowToast('Zadané údaje nejsou validní', 'error', 3000, 'top-end');
+    };
+
+    return (
+        <Modal
+            title="Upravit uživatele"
+            onOk={handleOk}
+            visible={true}
+            onCancel={handleCancel}
+            footer={null}
+        >
+            <Form
+                onFinish={onFinish}
+                onFinishFailed={onFinishFailed}
+                autoComplete="off"
+                labelCol={{ span: 4 }}
+                layout="horizontal"
+            >
+                <Form.Item
+                    label=""
+                    name="role"
+                    rules={[
+                        {
+                            required: true,
+                            message: 'Prosím vyberte roli',
+                        },
+                    ]}
+                >
+                    <Radio.Group>
+                        <Radio.Button value={ERole.Annotator}>Anotátor</Radio.Button>
+                        <Radio.Button value={ERole.Administrator}>Admin</Radio.Button>
+                    </Radio.Group>
+                </Form.Item>
+                <Form.Item
+                    name="username"
+                    initialValue={userInfo.username}
+                    rules={[
+                        {
+                            required: true,
+                            message: 'Prosím zadejte uživatelské jméno',
+                        },
+                    ]}
+                >
+                    <Input placeholder="Uživatelské jméno" />
+                </Form.Item>
+                <Form.Item
+                    name="password"
+                    rules={[{ required: true, message: 'Prosím zadejte heslo' }]}
+                >
+                    <Input.Password placeholder="Heslo" />
+                </Form.Item>
+
+                <Form.Item
+                    name="name"
+                    initialValue={userInfo.name}
+                    rules={[
+                        {
+                            required: true,
+                            message: 'Prosím zadejte jméno',
+                        },
+                    ]}
+                >
+                    <Input placeholder="Jméno" />
+                </Form.Item>
+
+                <Form.Item
+                    name="surname"
+                    initialValue={userInfo.surname}
+                    rules={[
+                        {
+                            required: true,
+                            message: 'Prosím zadejte příjmení',
+                        },
+                    ]}
+                >
+                    <Input placeholder="Příjmení" />
+                </Form.Item>
+                <Form.Item>
+                    <Button type="primary" htmlType="submit" className="w-100">
+                        Upravit
+                    </Button>
+                </Form.Item>
+            </Form>
+        </Modal>
+    );
+};
+
+export default EditUserModal;
diff --git a/webapp/components/modals/UserDocumentsModal.tsx b/webapp/components/modals/UserDocumentsModal.tsx
new file mode 100644
index 0000000..45109cd
--- /dev/null
+++ b/webapp/components/modals/UserDocumentsModal.tsx
@@ -0,0 +1,60 @@
+import { List, Modal } from 'antd';
+import 'antd/dist/antd.css';
+import React, { useContext, useEffect, useState } from 'react';
+import { AnnotationListInfo } from '../../api';
+import { userController } from '../../controllers';
+import { useUnauthRedirect } from '../../hooks';
+import { LoggedUserContext } from '../../contexts/LoggedUserContext';
+
+interface ModalProps {
+    onCancel: () => void;
+    userId: string | undefined;
+}
+
+function UserDocumentsModal({ onCancel, userId }: ModalProps) {
+    const redirecting = useUnauthRedirect('/login');
+    const { role } = useContext(LoggedUserContext);
+    const [annotations, setAnnotations] = useState<AnnotationListInfo[]>([]);
+
+    const handleOk = () => {
+        onCancel();
+    };
+
+    const handleCancel = () => {
+        onCancel();
+    };
+
+    useEffect(() => {
+        if (!redirecting && role === 'ADMINISTRATOR' && userId) {
+            userController
+                .userUserIdAnnotationsGet(userId)
+                .then(({ data: { annotations } }) => {
+                    setAnnotations(annotations || []);
+                });
+        }
+    }, [userId, redirecting, role]);
+
+    return (
+        <Modal
+            title="Dokumenty uživatele"
+            onOk={handleOk}
+            visible={true}
+            onCancel={handleCancel}
+            footer={[null]}
+        >
+            <List
+                dataSource={annotations}
+                size={'small'}
+                grid={{ gutter: 10, column: 1 }}
+                style={{ maxHeight: 400, overflowY: 'auto', overflowX: 'hidden' }}
+                renderItem={(item) => (
+                    <List.Item key={item.annotationId}>
+                        <List.Item.Meta title={item.documentName} />
+                    </List.Item>
+                )}
+            />
+        </Modal>
+    );
+}
+
+export default UserDocumentsModal;
diff --git a/webapp/components/types/TableDocInfo.tsx b/webapp/components/types/TableDocInfo.tsx
new file mode 100644
index 0000000..2aa557d
--- /dev/null
+++ b/webapp/components/types/TableDocInfo.tsx
@@ -0,0 +1,10 @@
+import { UserInfo } from '../../api';
+
+export type TableDocInfo = {
+    id?: string;
+    key?: string;
+    name?: string | null;
+    length?: number;
+    requiredAnnotations?: number;
+    annotatingUsers?: Array<UserInfo> | null;
+};
diff --git a/webapp/pages/documents/admin/index.tsx b/webapp/pages/documents/admin/index.tsx
index d223d3a..2ea195f 100644
--- a/webapp/pages/documents/admin/index.tsx
+++ b/webapp/pages/documents/admin/index.tsx
@@ -13,6 +13,7 @@ import { AnnotationListInfo, DocumentListInfo, UserInfo } from '../../../api';
 import { documentController, userController } from '../../../controllers';
 import AssignDocumentModal from '../../../components/modals/AssignDocumentModal';
 import { ShowToast } from '../../../utils/alerts';
+import { TableDocInfo } from '../../../components/types/TableDocInfo';
 
 function AdminDocumentPage() {
     const redirecting = useUnauthRedirect('/login');
@@ -21,21 +22,25 @@ function AdminDocumentPage() {
     const [visibleAssign, setVisibleAssign] = React.useState(false);
     const router = useRouter();
 
-    const [documents, setDocuments] = useState<DocumentListInfo[]>([]);
+    const [documents, setDocuments] = useState<TableDocInfo[]>([]);
     const [selectedDocs, setSelectedDocs] = useState<string[] | undefined[]>([]);
 
-    useEffect(() => {
-        async function fetchData() {
-            let docs = (await documentController.documentsGet(0, 1000)).data.documents;
-            docs?.forEach((doc) => (doc.key = doc.id));
+    async function fetchData() {
+        const docs = (await documentController.documentsGet(0, 1000)).data.documents;
+
+        // @ts-ignore
+        const tableDocs: TableDocInfo[] = docs?.map((doc, index) => {
+            return { key: index, ...doc };
+        });
 
-            if (!docs) {
-                setDocuments([]);
-            } else {
-                setDocuments(docs);
-            }
+        if (!docs) {
+            setDocuments([]);
+        } else {
+            setDocuments(tableDocs);
         }
+    }
 
+    useEffect(() => {
         if (!redirecting && role === 'ADMINISTRATOR') {
             fetchData();
         }
@@ -54,6 +59,7 @@ function AdminDocumentPage() {
     };
 
     const hideModal = () => {
+        fetchData();
         setVisibleAdd(false);
         setVisibleAssign(false);
     };
@@ -96,6 +102,7 @@ function AdminDocumentPage() {
 
     const rowSelection = {
         onChange: (selectedRowKeys: React.Key[], selectedRows: DocumentListInfo[]) => {
+            // @ts-ignore
             setSelectedDocs(selectedRows.map((row) => row.id));
         },
     };
diff --git a/webapp/pages/users/index.tsx b/webapp/pages/users/index.tsx
index 6dbe101..c924a85 100644
--- a/webapp/pages/users/index.tsx
+++ b/webapp/pages/users/index.tsx
@@ -11,36 +11,67 @@ import { MainLayout } from '../../layouts/MainLayout';
 import { UserInfo } from '../../api';
 import { userController } from '../../controllers';
 import AddUserModal from '../../components/modals/AddUserModal';
+import UserDocumentsModal from '../../components/modals/UserDocumentsModal';
+import { ShowConfirmDelete } from '../../utils/alerts';
+import EditUserModal from '../../components/modals/EditUserModal';
 
 function UsersPage() {
     const redirecting = useUnauthRedirect('/login');
     const { logout, role } = useContext(LoggedUserContext);
     const router = useRouter();
 
-    const [visible, setVisible] = React.useState(false);
+    const [addVisible, setAddVisible] = React.useState(false);
+    const [editVisible, setEditVisible] = React.useState(false);
+    const [docsVisible, setDocsVisible] = React.useState(false);
     const [users, setUsers] = useState<UserInfo[]>([]);
 
-    useEffect(() => {
-        async function fetchData() {
-            let usrs = (await userController.usersGet()).data.users;
-            if (!usrs) {
-                setUsers([]);
-            } else {
-                setUsers(usrs);
-            }
+    const [user, setUser] = useState<UserInfo>({});
+
+    async function fetchData() {
+        let usrs = (await userController.usersGet()).data.users;
+        if (!usrs) {
+            setUsers([]);
+        } else {
+            setUsers(usrs);
         }
+    }
 
+    useEffect(() => {
         if (!redirecting && role === 'ADMINISTRATOR') {
             fetchData();
         }
     }, [logout, redirecting, role, router]);
 
-    const showModal = () => {
-        setVisible(true);
+    const setUserInfo = (userId: string) => {
+        const userInfo = users.find((user) => user.id === userId);
+        if (userInfo) {
+            setUser(userInfo);
+        }
+    };
+
+    const deleteUser = (userId: string) => {
+        ShowConfirmDelete(() => {
+            userController.userUserIdDelete(userId).then(() => fetchData());
+        }, 'uživatele');
+    };
+
+    const showAddModal = () => {
+        setAddVisible(true);
+    };
+    const showEditModal = (userId: string) => {
+        setUserInfo(userId);
+        setEditVisible(true);
+    };
+    const showDocsModal = (userId: string) => {
+        setUserInfo(userId);
+        setDocsVisible(true);
     };
 
-    const hideModal = () => {
-        setVisible(false);
+    const hideModals = () => {
+        fetchData();
+        setAddVisible(false);
+        setDocsVisible(false);
+        setEditVisible(false);
     };
 
     const columns = [
@@ -50,12 +81,13 @@ function UsersPage() {
         {
             title: '',
             key: 'operation',
-            render: () => (
+            dataIndex: 'id',
+            render: (id: string) => (
                 <div>
                     <Space size={'middle'}>
-                        <Button>Dokumenty</Button>
-                        <Button>Upravit</Button>
-                        <Button>Smazat</Button>
+                        <Button onClick={() => showDocsModal(id)}>Dokumenty</Button>
+                        <Button onClick={() => showEditModal(id)}>Upravit</Button>
+                        <Button onClick={() => deleteUser(id)}>Smazat</Button>
                     </Space>
                 </div>
             ),
@@ -67,10 +99,12 @@ function UsersPage() {
             <Typography.Title level={2}>
                 <FontAwesomeIcon icon={faUsers} /> Uživatelé
             </Typography.Title>
-            <Button type={'primary'} onClick={showModal}>
+            <Button type={'primary'} onClick={showAddModal}>
                 Přidat uživatele
             </Button>
-            {visible && <AddUserModal onCancel={hideModal} />}
+            {addVisible && <AddUserModal onCancel={hideModals} />}
+            {editVisible && <EditUserModal userInfo={user} onCancel={hideModals} />}
+            {docsVisible && <UserDocumentsModal userId={user.id} onCancel={hideModals} />}
 
             <Table
                 columns={columns}
diff --git a/webapp/utils/alerts.ts b/webapp/utils/alerts.ts
index c8c43ab..7a26ac6 100644
--- a/webapp/utils/alerts.ts
+++ b/webapp/utils/alerts.ts
@@ -1,5 +1,6 @@
 import Swal, { SweetAlertIcon, SweetAlertPosition } from 'sweetalert2';
 import withReactContent from 'sweetalert2-react-content';
+import { AxiosResponse } from 'axios';
 
 const MySwal = withReactContent(Swal);
 
@@ -26,3 +27,15 @@ export function ShowToast(
         title: text,
     });
 }
+
+export function ShowConfirmDelete(deleteAction: () => void, deleteSubjectLabel: string) {
+    Swal.fire({
+        title: 'Opravdu si přejete smazat ' + deleteSubjectLabel + '?',
+        showCancelButton: true,
+        confirmButtonText: 'Smazat',
+    }).then((result) => {
+        if (result.isConfirmed) {
+            deleteAction();
+        }
+    });
+}
