commit c4f198c309e80419f8a5689cd86604d42b068426
Author: Jaroslav Hrubý <hrubyj@students.zcu.cz>
Date:   Sun May 15 21:20:45 2022 +0200

    Finalization - buttons, confirm toasters and navigation were implemented

diff --git a/webapp/pages/documents/admin/index.tsx b/webapp/pages/documents/admin/index.tsx
index 710747c..27e6018 100644
--- a/webapp/pages/documents/admin/index.tsx
+++ b/webapp/pages/documents/admin/index.tsx
@@ -3,7 +3,7 @@ import React, { useContext, useEffect, useState } from 'react';
 
 import { useUnauthRedirect } from '../../../hooks';
 import { useRouter } from 'next/router';
-import { Button, Space, Table, Typography } from 'antd';
+import { Button, Row, Space, Table, Tag, Typography } from 'antd';
 import { faFileLines, faUser } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { LoggedUserContext } from '../../../contexts/LoggedUserContext';
@@ -17,10 +17,11 @@ import {
 } from '../../../api';
 import { documentController, userController } from '../../../controllers';
 import AssignDocumentModal from '../../../components/modals/AssignDocumentModal';
-import { ShowConfirmDelete, ShowToast } from '../../../utils/alerts';
+import { ShowConfirm, ShowToast } from '../../../utils/alerts';
 import { TableDocInfo } from '../../../components/types/TableDocInfo';
 import {
     getAnnotationStateColor,
+    getAnnotationStateString,
     getNameTruncated,
     getUserInfoAlt,
 } from '../../../utils/strings';
@@ -29,12 +30,17 @@ import SetRequiredAnnotationsCountModal from '../../../components/modals/SetRequ
 import DocPreviewModal from '../../../components/modals/DocPreviewModal';
 import { UserFilter } from '../../../components/types/UserFilter';
 import { getColumnSearchProps, getLocaleProps } from '../../../utils/tableUtils';
-import { UserOutlined } from '@ant-design/icons';
-import Swal from 'sweetalert2';
+import {
+    CheckCircleOutlined,
+    ClockCircleOutlined,
+    SyncOutlined,
+} from '@ant-design/icons';
+import { SweetAlertIcon } from 'sweetalert2';
 
 function AdminDocumentPage() {
     const redirecting = useUnauthRedirect('/login');
     const { logout, role } = useContext(LoggedUserContext);
+    const [finalizing, setFinalizing] = React.useState(false);
     const [visibleAdd, setVisibleAdd] = React.useState(false);
     const [visibleAssign, setVisibleAssign] = React.useState(false);
     const [visiblePreview, setVisiblePreview] = React.useState(false);
@@ -78,6 +84,94 @@ function AdminDocumentPage() {
         }
     }, [logout, redirecting, role, router]);
 
+    const finalizeDocumentConfirm = async (
+        document: DocumentListInfo,
+        recreate: boolean
+    ) => {
+        let desc = recreate ? 'Dosavadní změny finální verze budou smazány' : '';
+        let icon: SweetAlertIcon = recreate ? 'warning' : 'question';
+
+        const doneAnnotations = document.annotatingUsers?.filter(
+            (usr) => usr.state === EState.Done
+        ).length;
+
+        if (
+            doneAnnotations !== undefined &&
+            document.requiredAnnotations !== undefined &&
+            doneAnnotations < document.requiredAnnotations
+        ) {
+            icon = 'warning';
+            desc =
+                'Není dokončen požadovaný počet anotací <br /> (dokončeno ' +
+                doneAnnotations +
+                ' z ' +
+                document.requiredAnnotations +
+                ')';
+        }
+        recreate
+            ? ShowConfirm(
+                  () => finalizeDocument(document.id),
+                  'vytvořit novou finální verzi dokumentu',
+                  desc,
+                  icon
+              )
+            : ShowConfirm(
+                  () => finalizeDocument(document.id),
+                  'vytvořit finální verzi dokumentu',
+                  desc,
+                  icon
+              );
+    };
+
+    const finalizeDocument = async (documentId: string | undefined) => {
+        setFinalizing(true);
+        const finalAnnotationId = (
+            await documentController.documentDocumentIdFinalPost(
+                documentId ? documentId : ''
+            )
+        ).data.finalAnnotationId;
+        if (!finalAnnotationId) {
+            ShowToast('Finální verzi se nepovedlo vytvořit', 'error');
+        } else {
+            router.push({
+                pathname: '/annotation/[annotationId]',
+                query: { annotationId: finalAnnotationId, final: true },
+            });
+        }
+        setFinalizing(false);
+    };
+
+    const editFinalizedDocument = async (finalAnnotationId: string) => {
+        setFinalizing(true);
+        if (!finalAnnotationId) {
+            ShowToast('Finální verze dosud neexistuje', 'warning');
+        } else {
+            router.push({
+                pathname: '/annotation/[annotationId]',
+                query: { annotationId: finalAnnotationId, final: true },
+            });
+        }
+        setFinalizing(false);
+    };
+
+    const getFinalizationStateIcon = (state: EState) => {
+        const color = getAnnotationStateColor(state);
+        const label = getAnnotationStateString(state);
+        let icon = <CheckCircleOutlined />;
+        if (state === 'NEW') {
+            icon = <ClockCircleOutlined />;
+        }
+        if (state === 'IN_PROGRESS') {
+            icon = <SyncOutlined />;
+        }
+
+        return (
+            <Tag icon={icon} color={color} key={label}>
+                {label.toUpperCase()}
+            </Tag>
+        );
+    };
+
     const showAssignModal = () => {
         if (selectedDocs.length == 0) {
             ShowToast('Vyberte dokument pro přiřazení', 'warning', 3000, 'top-end');
@@ -124,6 +218,7 @@ function AdminDocumentPage() {
             title: 'Název dokumentu',
             dataIndex: 'name',
             key: 'name',
+            width: '15%',
             ...getColumnSearchProps('name', 'název'),
             sorter: {
                 // @ts-ignore
@@ -135,6 +230,8 @@ function AdminDocumentPage() {
             title: 'Délka',
             dataIndex: 'length',
             key: 'length',
+            width: '6%',
+            align: 'center' as 'center',
             sorter: {
                 // @ts-ignore
                 compare: (a, b) => a.length - b.length,
@@ -144,6 +241,8 @@ function AdminDocumentPage() {
         {
             title: 'Dokončeno | přiřazeno | vyžadováno',
             key: 'annotationCounts',
+            width: '15%',
+            align: 'center' as 'center',
             render: (
                 columnData: DocumentListResponse,
                 record: DocumentListInfo,
@@ -187,6 +286,7 @@ function AdminDocumentPage() {
             title: 'Anotátoři',
             dataIndex: 'annotatingUsers',
             key: 'annotatingUsers',
+            width: '20%',
             render: (
                 columnData: DocumentUserInfo[],
                 record: DocumentListInfo,
@@ -209,7 +309,13 @@ function AdminDocumentPage() {
                                     title={getUserInfoAlt(e)}
                                     className={'me-2'}
                                 />
-                                {getNameTruncated(e)}
+                                {record.finalAnnotations?.some(
+                                    (annot) => annot.userId === e.id
+                                ) ? (
+                                    <u>{getNameTruncated(e)}</u>
+                                ) : (
+                                    getNameTruncated(e)
+                                )}
                             </span>
                         ))}
                     </div>
@@ -231,6 +337,8 @@ function AdminDocumentPage() {
             title: '',
             key: 'action',
             dataIndex: ['id', 'name'],
+            width: '10%',
+            align: 'center' as 'center',
             // @ts-ignore
             render: (text, row) => (
                 <Button key={row.id} onClick={() => showPreviewModal(row.id, row.name)}>
@@ -238,6 +346,61 @@ function AdminDocumentPage() {
                 </Button>
             ),
         },
+        {
+            title: 'Finální verze dokumentu',
+            key: 'final',
+            dataIndex: ['id', 'finalizedExists'],
+            // @ts-ignore
+            render: (text, row) =>
+                row.finalizedExists ? (
+                    <Row>
+                        <Space>
+                            <Button
+                                disabled={finalizing}
+                                onClick={() => finalizeDocumentConfirm(row.id, true)}
+                            >
+                                Znovu vytvořit
+                            </Button>
+                            <Button
+                                disabled={finalizing}
+                                onClick={() =>
+                                    editFinalizedDocument(row.finalizedAnnotationId)
+                                }
+                            >
+                                Upravit
+                            </Button>
+                            {getFinalizationStateIcon(row.finalizedState)}
+                        </Space>
+                    </Row>
+                ) : (
+                    <Button
+                        disabled={finalizing}
+                        onClick={() => finalizeDocumentConfirm(row, false)}
+                    >
+                        Finalizovat
+                    </Button>
+                ),
+            filters: [
+                {
+                    text: 'Nefinalizováno',
+                    value: null,
+                },
+                {
+                    text: 'Nový',
+                    value: 'NEW',
+                },
+                {
+                    text: 'Rozpracováno',
+                    value: 'IN_PROGRESS',
+                },
+                {
+                    text: 'Hotovo',
+                    value: 'DONE',
+                },
+            ],
+            // @ts-ignore
+            onFilter: (value, record) => record.finalizedState === value,
+        },
     ];
 
     const rowSelection = {
diff --git a/webapp/utils/alerts.ts b/webapp/utils/alerts.ts
index f4238c0..9ee59b0 100644
--- a/webapp/utils/alerts.ts
+++ b/webapp/utils/alerts.ts
@@ -43,10 +43,16 @@ export function ShowConfirmDelete(deleteAction: () => void, deleteSubjectLabel:
     });
 }
 
-export function ShowConfirm(action: () => void, actionLabel: string) {
+export function ShowConfirm(
+    action: () => void,
+    actionLabel: string,
+    description: string = '',
+    icon: SweetAlertIcon = 'question'
+) {
     Swal.fire({
-        icon: 'question',
+        icon: icon,
         title: 'Opravdu si přejete ' + actionLabel + '?',
+        html: description,
         showCancelButton: true,
         confirmButtonText: 'Potvrdit',
         confirmButtonColor: '#5278ff',
