commit b80a69199b38fd9dc128cd0afa4fcd28c31b2f66
Author: Lukáš Vlček <vlcek@net-inout.cz>
Date:   Sun May 15 22:36:31 2022 +0200

    Document finalization

diff --git a/webapp/api/api.ts b/webapp/api/api.ts
index 3ea0bc5..2e6b1e5 100644
--- a/webapp/api/api.ts
+++ b/webapp/api/api.ts
@@ -47,6 +47,121 @@ export interface AddNoteToTagOccurenceRequest {
      */
     'note'?: string | null;
 }
+/**
+ * 
+ * @export
+ * @interface Annotation
+ */
+export interface Annotation {
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {Document}
+     * @memberof Annotation
+     */
+    'document'?: Document;
+    /**
+     * 
+     * @type {User}
+     * @memberof Annotation
+     */
+    'user'?: User;
+    /**
+     * 
+     * @type {User}
+     * @memberof Annotation
+     */
+    'userAssigned'?: User;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'dateAssigned'?: string;
+    /**
+     * 
+     * @type {EState}
+     * @memberof Annotation
+     */
+    'state'?: EState;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'dateLastChanged'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'note'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'cachedDocumentHTML'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'cachedStartPositions'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'cachedLengths'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'cachedClosingPositions'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'cachedClosingLengths'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'cachedCSS'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Annotation
+     */
+    'lastModifiedTagId'?: string | null;
+    /**
+     * 
+     * @type {EModified}
+     * @memberof Annotation
+     */
+    'modifiedType'?: EModified;
+    /**
+     * 
+     * @type {Array<Class>}
+     * @memberof Annotation
+     */
+    'classes'?: Array<Class> | null;
+    /**
+     * 
+     * @type {Array<FinalAnnotation>}
+     * @memberof Annotation
+     */
+    'finalAnnotations'?: Array<FinalAnnotation> | null;
+}
 /**
  * 
  * @export
@@ -245,6 +360,43 @@ export interface ChangeUserInfoRequest {
      */
     'role'?: ERole;
 }
+/**
+ * 
+ * @export
+ * @interface Class
+ */
+export interface Class {
+    /**
+     * 
+     * @type {string}
+     * @memberof Class
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof Class
+     */
+    'name'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Class
+     */
+    'description'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Class
+     */
+    'color'?: string | null;
+    /**
+     * 
+     * @type {Array<Annotation>}
+     * @memberof Class
+     */
+    'annotations'?: Array<Annotation> | null;
+}
 /**
  * 
  * @export
@@ -419,6 +571,55 @@ export interface CreateUserRequest {
      */
     'role'?: ERole;
 }
+/**
+ * 
+ * @export
+ * @interface Document
+ */
+export interface Document {
+    /**
+     * 
+     * @type {string}
+     * @memberof Document
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof Document
+     */
+    'name'?: string | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof Document
+     */
+    'length'?: number;
+    /**
+     * 
+     * @type {DocumentContent}
+     * @memberof Document
+     */
+    'content'?: DocumentContent;
+    /**
+     * 
+     * @type {string}
+     * @memberof Document
+     */
+    'dateAdded'?: string;
+    /**
+     * 
+     * @type {User}
+     * @memberof Document
+     */
+    'userAdded'?: User;
+    /**
+     * 
+     * @type {number}
+     * @memberof Document
+     */
+    'requiredAnnotations'?: number;
+}
 /**
  * 
  * @export
@@ -494,6 +695,25 @@ export interface DocumentAnnotationInfo {
      */
     'username'?: string | null;
 }
+/**
+ * 
+ * @export
+ * @interface DocumentContent
+ */
+export interface DocumentContent {
+    /**
+     * 
+     * @type {string}
+     * @memberof DocumentContent
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof DocumentContent
+     */
+    'content'?: string | null;
+}
 /**
  * 
  * @export
@@ -664,6 +884,21 @@ export const EDocumentType = {
 export type EDocumentType = typeof EDocumentType[keyof typeof EDocumentType];
 
 
+/**
+ * 
+ * @export
+ * @enum {string}
+ */
+
+export const EModified = {
+    Added: 'ADDED',
+    Removed: 'REMOVED',
+    None: 'NONE'
+} as const;
+
+export type EModified = typeof EModified[keyof typeof EModified];
+
+
 /**
  * 
  * @export
@@ -722,6 +957,206 @@ export const ETagType = {
 export type ETagType = typeof ETagType[keyof typeof ETagType];
 
 
+/**
+ * 
+ * @export
+ * @interface FinalAnnotation
+ */
+export interface FinalAnnotation {
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {Document}
+     * @memberof FinalAnnotation
+     */
+    'document'?: Document;
+    /**
+     * 
+     * @type {User}
+     * @memberof FinalAnnotation
+     */
+    'user'?: User;
+    /**
+     * 
+     * @type {User}
+     * @memberof FinalAnnotation
+     */
+    'userAssigned'?: User;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'dateAssigned'?: string;
+    /**
+     * 
+     * @type {EState}
+     * @memberof FinalAnnotation
+     */
+    'state'?: EState;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'dateLastChanged'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'note'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'cachedDocumentHTML'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'cachedStartPositions'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'cachedLengths'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'cachedClosingPositions'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'cachedClosingLengths'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'cachedCSS'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotation
+     */
+    'lastModifiedTagId'?: string | null;
+    /**
+     * 
+     * @type {EModified}
+     * @memberof FinalAnnotation
+     */
+    'modifiedType'?: EModified;
+    /**
+     * 
+     * @type {Array<Class>}
+     * @memberof FinalAnnotation
+     */
+    'classes'?: Array<Class> | null;
+    /**
+     * 
+     * @type {Array<FinalAnnotation>}
+     * @memberof FinalAnnotation
+     */
+    'finalAnnotations'?: Array<FinalAnnotation> | null;
+    /**
+     * 
+     * @type {Array<Annotation>}
+     * @memberof FinalAnnotation
+     */
+    'annotations'?: Array<Annotation> | null;
+}
+/**
+ * 
+ * @export
+ * @interface FinalAnnotationTag
+ */
+export interface FinalAnnotationTag {
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotationTag
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {Tag}
+     * @memberof FinalAnnotationTag
+     */
+    'tag'?: Tag;
+    /**
+     * 
+     * @type {SubTag}
+     * @memberof FinalAnnotationTag
+     */
+    'subTag'?: SubTag;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotationTag
+     */
+    'instance'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotationTag
+     */
+    'note'?: string | null;
+    /**
+     * 
+     * @type {number}
+     * @memberof FinalAnnotationTag
+     */
+    'position'?: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof FinalAnnotationTag
+     */
+    'length'?: number;
+    /**
+     * 
+     * @type {ETagSentiment}
+     * @memberof FinalAnnotationTag
+     */
+    'sentiment'?: ETagSentiment;
+    /**
+     * 
+     * @type {string}
+     * @memberof FinalAnnotationTag
+     */
+    'selectedText'?: string | null;
+    /**
+     * 
+     * @type {FinalAnnotation}
+     * @memberof FinalAnnotationTag
+     */
+    'annotation'?: FinalAnnotation;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof FinalAnnotationTag
+     */
+    'isFinal'?: boolean;
+    /**
+     * 
+     * @type {Array<User>}
+     * @memberof FinalAnnotationTag
+     */
+    'users'?: Array<User> | null;
+}
 /**
  * 
  * @export
@@ -924,6 +1359,19 @@ export interface ProblemDetails {
      */
     'instance'?: string | null;
 }
+/**
+ * 
+ * @export
+ * @interface SetInstanceIsFinalRequest
+ */
+export interface SetInstanceIsFinalRequest {
+    /**
+     * 
+     * @type {boolean}
+     * @memberof SetInstanceIsFinalRequest
+     */
+    'isFinal'?: boolean;
+}
 /**
  * 
  * @export
@@ -969,6 +1417,43 @@ export interface SetRequiredAnnotationsRequest {
      */
     'documentIds'?: Array<string> | null;
 }
+/**
+ * 
+ * @export
+ * @interface SubTag
+ */
+export interface SubTag {
+    /**
+     * 
+     * @type {string}
+     * @memberof SubTag
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof SubTag
+     */
+    'name'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof SubTag
+     */
+    'description'?: string | null;
+    /**
+     * 
+     * @type {Tag}
+     * @memberof SubTag
+     */
+    'tag'?: Tag;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof SubTag
+     */
+    'sentimentEnabled'?: boolean;
+}
 /**
  * 
  * @export
@@ -994,6 +1479,86 @@ export interface SubTagInfo {
      */
     'description'?: string | null;
 }
+/**
+ * 
+ * @export
+ * @interface Tag
+ */
+export interface Tag {
+    /**
+     * 
+     * @type {string}
+     * @memberof Tag
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {TagCategory}
+     * @memberof Tag
+     */
+    'category'?: TagCategory;
+    /**
+     * 
+     * @type {string}
+     * @memberof Tag
+     */
+    'name'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Tag
+     */
+    'description'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof Tag
+     */
+    'color'?: string | null;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof Tag
+     */
+    'sentimentEnabled'?: boolean;
+}
+/**
+ * 
+ * @export
+ * @interface TagCategory
+ */
+export interface TagCategory {
+    /**
+     * 
+     * @type {string}
+     * @memberof TagCategory
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof TagCategory
+     */
+    'name'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof TagCategory
+     */
+    'color'?: string | null;
+    /**
+     * 
+     * @type {string}
+     * @memberof TagCategory
+     */
+    'description'?: string | null;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof TagCategory
+     */
+    'disabledForAnnotators'?: boolean;
+}
 /**
  * 
  * @export
@@ -1183,6 +1748,18 @@ export interface TagInstanceInfo {
      * @memberof TagInstanceInfo
      */
     'selectedText'?: string | null;
+    /**
+     * 
+     * @type {Array<UserInfo>}
+     * @memberof TagInstanceInfo
+     */
+    'users'?: Array<UserInfo> | null;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof TagInstanceInfo
+     */
+    'isFinal'?: boolean | null;
 }
 /**
  * 
@@ -1233,6 +1810,12 @@ export interface User {
      * @memberof User
      */
     'role'?: ERole;
+    /**
+     * 
+     * @type {Array<FinalAnnotationTag>}
+     * @memberof User
+     */
+    'finalAnnotationTags'?: Array<FinalAnnotationTag> | null;
 }
 /**
  * 
@@ -1507,6 +2090,47 @@ export const AnnotationApiAxiosParamCreator = function (configuration?: Configur
                 options: localVarRequestOptions,
             };
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {string} occurenceId 
+         * @param {SetInstanceIsFinalRequest} [setInstanceIsFinalRequest] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        annotationAnnotationIdOccurenceIdFinalPut: async (annotationId: string, occurenceId: string, setInstanceIsFinalRequest?: SetInstanceIsFinalRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'annotationId' is not null or undefined
+            assertParamExists('annotationAnnotationIdOccurenceIdFinalPut', 'annotationId', annotationId)
+            // verify required parameter 'occurenceId' is not null or undefined
+            assertParamExists('annotationAnnotationIdOccurenceIdFinalPut', 'occurenceId', occurenceId)
+            const localVarPath = `/annotation/{annotationId}/{occurenceId}/final`
+                .replace(`{${"annotationId"}}`, encodeURIComponent(String(annotationId)))
+                .replace(`{${"occurenceId"}}`, encodeURIComponent(String(occurenceId)));
+            // 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(setInstanceIsFinalRequest, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
         /**
          * 
          * @param {string} annotationId 
@@ -1698,6 +2322,18 @@ export const AnnotationApiFp = function(configuration?: Configuration) {
             const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, isFinal, options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {string} occurenceId 
+         * @param {SetInstanceIsFinalRequest} [setInstanceIsFinalRequest] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async annotationAnnotationIdOccurenceIdFinalPut(annotationId: string, occurenceId: string, setInstanceIsFinalRequest?: SetInstanceIsFinalRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdOccurenceIdFinalPut(annotationId, occurenceId, setInstanceIsFinalRequest, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
         /**
          * 
          * @param {string} annotationId 
@@ -1798,6 +2434,17 @@ export const AnnotationApiFactory = function (configuration?: Configuration, bas
         annotationAnnotationIdOccurenceIdDelete(annotationId: string, occurenceId: string, isFinal?: boolean, options?: any): AxiosPromise<void> {
             return localVarFp.annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, isFinal, options).then((request) => request(axios, basePath));
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {string} occurenceId 
+         * @param {SetInstanceIsFinalRequest} [setInstanceIsFinalRequest] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        annotationAnnotationIdOccurenceIdFinalPut(annotationId: string, occurenceId: string, setInstanceIsFinalRequest?: SetInstanceIsFinalRequest, options?: any): AxiosPromise<void> {
+            return localVarFp.annotationAnnotationIdOccurenceIdFinalPut(annotationId, occurenceId, setInstanceIsFinalRequest, options).then((request) => request(axios, basePath));
+        },
         /**
          * 
          * @param {string} annotationId 
@@ -1905,6 +2552,19 @@ export class AnnotationApi extends BaseAPI {
         return AnnotationApiFp(this.configuration).annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, isFinal, options).then((request) => request(this.axios, this.basePath));
     }
 
+    /**
+     * 
+     * @param {string} annotationId 
+     * @param {string} occurenceId 
+     * @param {SetInstanceIsFinalRequest} [setInstanceIsFinalRequest] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AnnotationApi
+     */
+    public annotationAnnotationIdOccurenceIdFinalPut(annotationId: string, occurenceId: string, setInstanceIsFinalRequest?: SetInstanceIsFinalRequest, options?: AxiosRequestConfig) {
+        return AnnotationApiFp(this.configuration).annotationAnnotationIdOccurenceIdFinalPut(annotationId, occurenceId, setInstanceIsFinalRequest, options).then((request) => request(this.axios, this.basePath));
+    }
+
     /**
      * 
      * @param {string} annotationId 
diff --git a/webapp/components/annotation/AnnotationItem.tsx b/webapp/components/annotation/AnnotationItem.tsx
index 7952b5b..ef1c283 100644
--- a/webapp/components/annotation/AnnotationItem.tsx
+++ b/webapp/components/annotation/AnnotationItem.tsx
@@ -1,18 +1,15 @@
 import { Col, Container, Row, Stack } from 'react-bootstrap';
 import { Tag } from '../types/tag';
-import { ChangeEvent, useContext, useState } from 'react';
+import { useContext, useState } from 'react';
 import 'antd/dist/antd.css';
-import { Button, Input, Select } from 'antd';
-import {
-    PlusOutlined,
-    DownOutlined,
-    DeleteOutlined,
-    TagOutlined,
-    EyeOutlined,
-} from '@ant-design/icons';
+import { Button, Select } from 'antd';
+import { DownOutlined, PlusOutlined, TagOutlined } from '@ant-design/icons';
 import { AnnotationContext } from '../../contexts/AnnotationContext';
-import { ETagSentiment, TagInstanceInfo } from '../../api';
-import { getTextMaxLength } from '../../utils/strings';
+import { AnnotationOccurrenceItem } from './AnnotationOccurrenceItem';
+import { COLOR_ALL_OCCURRENCES_ACCEPTED, COLOR_HIGHLIGHTED_TAG } from '../../constants';
+import { ABadge, BadgeStyle } from '../common/ABadge';
+import { getNameTruncated } from '../../utils/strings';
+import { ShowConfirm } from '../../utils/alerts';
 
 const { Option } = Select;
 
@@ -25,22 +22,18 @@ export function AnnotationItem(props: { tag: Tag }) {
     /**
      * Should properties of this annotation be visible?
      */
-    const [visibleProperties, setVisibleProperties] = useState(false);
+    const [detailsVisible, setDetailsVisible] = useState(false);
 
     /**
      * Context that manages annotations.
      */
     const {
         addOccurrence,
-        deleteOccurrence,
-        changePosition,
-        changeNote,
-        changeSentiment,
-        changeLength,
-        selectedOccurrenceId,
         selectedInstanceId,
         setSelectedOccurrenceId,
         setSelectedInstanceId,
+        isFinal,
+        makeOccurrenceFinal,
     } = useContext(AnnotationContext);
 
     /**
@@ -50,57 +43,40 @@ export function AnnotationItem(props: { tag: Tag }) {
         addOccurrence(props.tag);
     };
 
-    /**
-     * Removes an occurrence of this annotation from the context.
-     * @param occurrence The occurrence that should be removed.
-     */
-    const onDeleteOccurrence = (occurrence: TagInstanceInfo) => (e: any) => {
-        deleteOccurrence(occurrence);
-    };
-
-    /**
-     * Changes a position of an occurrence of this annotation in the context.
-     * @param occurrence The occurrence that should be changed.
-     */
-    const onChangePosition =
-        (occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLInputElement>) => {
-            changePosition(occurrence, Number(e.currentTarget.value));
-        };
-
-    /**
-     * Changes a length of an occurrence of this annotation in the context.
-     * @param occurrence The occurrence that should be changed.
-     */
-    const onChangeLength =
-        (occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLInputElement>) => {
-            changeLength(occurrence, Number(e.currentTarget.value));
-        };
-
-    const onChangeSentiment = (occurrence: TagInstanceInfo) => (val: ETagSentiment) => {
-        changeSentiment(occurrence, val);
-    };
-
-    const onChangeNote =
-        (occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLTextAreaElement>) => {
-            changeNote(occurrence, e.currentTarget.value);
-        };
-
     /**
      * Changes visibility of properties of this annotation.
      */
     const changePropertiesVisibility = () => {
-        setVisibleProperties(!visibleProperties);
+        setDetailsVisible(!detailsVisible);
     };
 
+    function isAllAccepted() {
+        return props.tag.occurrences.filter((o) => !o.isFinal).length === 0;
+    }
+    function getBackgroundColor(): string {
+        if (selectedInstanceId === props.tag.instanceId) {
+            // highlighted tag
+            return COLOR_HIGHLIGHTED_TAG;
+        }
+
+        if (isFinal) {
+            if (isAllAccepted()) {
+                return COLOR_ALL_OCCURRENCES_ACCEPTED;
+            } else {
+                return '#E4C8CC';
+            }
+        }
+
+        return 'white';
+    }
     return (
-        <Container>
-            <Row
-                className="border rounded"
-                style={{
-                    backgroundColor:
-                        selectedInstanceId === props.tag.instanceId ? '#FCF3CF' : 'white',
-                }}
-            >
+        <Container
+            className="border rounded"
+            style={{
+                backgroundColor: getBackgroundColor(),
+            }}
+        >
+            <Row className="">
                 <Col sm="auto" className="d-flex align-items-center">
                     <Button
                         icon={<TagOutlined style={{ marginLeft: 0 }} />}
@@ -111,10 +87,7 @@ export function AnnotationItem(props: { tag: Tag }) {
                         style={{
                             border: 'none',
                             paddingLeft: 0,
-                            backgroundColor:
-                                selectedInstanceId === props.tag.instanceId
-                                    ? '#FCF3CF'
-                                    : 'white',
+                            backgroundColor: getBackgroundColor(),
                         }}
                         title={'Zvýraznit značky'}
                     />
@@ -124,6 +97,12 @@ export function AnnotationItem(props: { tag: Tag }) {
                     {props.tag.subtagName ? ' (' + props.tag.subtagName + ')' : ''}
                 </Col>
                 <Col sm="auto">
+                    {props.tag.occurrences.length > 1 && (
+                        <ABadge style={BadgeStyle.GENERAL}>
+                            {props.tag.occurrences.length}
+                        </ABadge>
+                    )}
+
                     <Button
                         type="text"
                         shape="circle"
@@ -138,123 +117,77 @@ export function AnnotationItem(props: { tag: Tag }) {
                     />
                 </Col>
             </Row>
-            {visibleProperties && (
-                <Stack gap={1} className="mb-2">
-                    <div>Kategorie: {props.tag.category}</div>
-                    <div>Výskyty:</div>
-                    {props.tag.occurrences.map((occurrence, index) => {
-                        return (
-                            <Container
-                                key={index}
-                                className="shadow-sm"
-                                style={{
-                                    backgroundColor:
-                                        selectedOccurrenceId === occurrence.occurenceId
-                                            ? '#D8E1E9'
-                                            : 'white',
-                                }}
-                            >
-                                <Row className="mb-1 mt-1">
-                                    <Col>
-                                        <Row>
-                                            <Col>Pozice: {occurrence.position}</Col>
-                                            <Col>Délka: {occurrence.length}</Col>
-                                        </Row>
-                                        <Row>
-                                            <i title={occurrence.selectedText ?? ''}>
-                                                {getTextMaxLength(
-                                                    occurrence.selectedText ?? '',
-                                                    35
-                                                )}
-                                            </i>
-                                        </Row>
-                                        <Row>
-                                            <Col
-                                                className="d-flex align-items-center"
-                                                sm="4"
+            {isFinal && !isAllAccepted() && (
+                <>
+                    <Row
+                        style={{
+                            backgroundColor: getBackgroundColor(),
+                        }}
+                    >
+                        <Col sm="12" className="d-flex align-items-center">
+                            {props.tag.occurrences.length === 1 && (
+                                <div
+                                    style={{
+                                        display: 'flex',
+                                        flexDirection: 'row',
+                                        width: '100%',
+                                        justifyContent: 'space-between',
+                                    }}
+                                >
+                                    <div style={{ width: '60%' }}>
+                                        Anotovali:{' '}
+                                        {props.tag.occurrences[0].users?.map((u) => (
+                                            <span
+                                                title={
+                                                    u.name +
+                                                    ' ' +
+                                                    u.surname +
+                                                    ' (' +
+                                                    u.username +
+                                                    ')'
+                                                }
+                                                key={
+                                                    props.tag.occurrences[0].occurenceId +
+                                                    '.' +
+                                                    u.id
+                                                }
+                                                style={{ marginRight: 10 }}
                                             >
-                                                Poznámka:
-                                            </Col>
-                                            <Col>
-                                                <Input.TextArea
-                                                    defaultValue={occurrence.note ?? ''}
-                                                    onBlur={onChangeNote(occurrence)}
-                                                    rows={1}
-                                                />
-                                            </Col>
-                                        </Row>
-                                        {occurrence.sentiment && (
-                                            <Row>
-                                                <Col
-                                                    className="d-flex align-items-center"
-                                                    sm="4"
-                                                >
-                                                    Sentiment:
-                                                </Col>
-                                                <Col>
-                                                    <Select
-                                                        defaultValue={
-                                                            occurrence.sentiment
-                                                        }
-                                                        style={{ width: '100%' }}
-                                                        onChange={onChangeSentiment(
-                                                            occurrence
-                                                        )}
-                                                    >
-                                                        <Option
-                                                            value={ETagSentiment.Positive}
-                                                        >
-                                                            <span
-                                                                style={{ color: 'green' }}
-                                                            >
-                                                                Pozitivní
-                                                            </span>
-                                                        </Option>
-                                                        <Option
-                                                            value={ETagSentiment.Neutral}
-                                                        >
-                                                            Neutrální
-                                                        </Option>
-                                                        <Option
-                                                            value={ETagSentiment.Negative}
-                                                        >
-                                                            <span
-                                                                style={{
-                                                                    color: '#ff4d4f',
-                                                                }}
-                                                            >
-                                                                Negativní
-                                                            </span>
-                                                        </Option>
-                                                    </Select>
-                                                </Col>
-                                            </Row>
-                                        )}
-                                    </Col>
-                                    <Col
-                                        sm="auto"
-                                        className="d-flex align-items-center flex-column justify-content-sm-evenly"
-                                    >
+                                                {getNameTruncated(u)}
+                                            </span>
+                                        ))}
+                                    </div>
+                                    <div>
                                         <Button
-                                            icon={<EyeOutlined />}
                                             onClick={() => {
-                                                setSelectedOccurrenceId(
-                                                    occurrence.occurenceId ?? null
-                                                );
-                                                setSelectedInstanceId(
-                                                    props.tag.instanceId
-                                                );
+                                                ShowConfirm(() => {
+                                                    makeOccurrenceFinal(
+                                                        props.tag.occurrences[0]
+                                                    );
+                                                }, 'označit toto řešení jako správné');
                                             }}
-                                            title={'Zvýraznit výskyt'}
-                                        />
-                                        <Button
-                                            icon={<DeleteOutlined />}
-                                            onClick={onDeleteOccurrence(occurrence)}
-                                            danger
-                                        />
-                                    </Col>
-                                </Row>
-                            </Container>
+                                        >
+                                            Přijmout toto řešení
+                                        </Button>
+                                    </div>
+                                </div>
+                            )}
+                        </Col>
+                    </Row>
+                </>
+            )}
+
+            {detailsVisible && (
+                <Stack gap={1} className="mb-2">
+                    <div>Kategorie: {props.tag.category}</div>
+                    <div>Výskyty (části):</div>
+                    {props.tag.occurrences.map((occurrence, index) => {
+                        return (
+                            <AnnotationOccurrenceItem
+                                occurrence={occurrence}
+                                tag={props.tag}
+                                key={'occ-' + occurrence.occurenceId}
+                            />
                         );
                     })}
                 </Stack>
diff --git a/webapp/components/annotation/AnnotationOccurrenceItem.tsx b/webapp/components/annotation/AnnotationOccurrenceItem.tsx
new file mode 100644
index 0000000..9ea1f36
--- /dev/null
+++ b/webapp/components/annotation/AnnotationOccurrenceItem.tsx
@@ -0,0 +1,184 @@
+import { Col, Container, Row } from 'react-bootstrap';
+import { Tag } from '../types/tag';
+import { ChangeEvent, useContext } from 'react';
+import 'antd/dist/antd.css';
+import { Button, Input, Select } from 'antd';
+import { DeleteOutlined, EyeOutlined, LikeOutlined } from '@ant-design/icons';
+import { AnnotationContext } from '../../contexts/AnnotationContext';
+import { ETagSentiment, TagInstanceInfo } from '../../api';
+import { getNameTruncated, getTextMaxLength } from '../../utils/strings';
+import {
+    COLOR_ALL_OCCURRENCES_ACCEPTED,
+    COLOR_HIGHLIGHTED_OCCURRENCE,
+    COLOR_HIGHLIGHTED_TAG,
+    COLOR_OCCURRENCE_ACCEPTED,
+} from '../../constants';
+import { ShowConfirm } from '../../utils/alerts';
+
+const { Option } = Select;
+
+/**
+ * Creates a single item in an annotation panel.
+ * @param props Properties should contain a tag that will be shown in this annotation.
+ * @returns The item that represents an annotation.
+ */
+export function AnnotationOccurrenceItem(props: {
+    tag: Tag;
+    occurrence: TagInstanceInfo;
+}) {
+    /**
+     * Context that manages annotations.
+     */
+    const {
+        deleteOccurrence,
+        changeNote,
+        changeSentiment,
+        selectedOccurrenceId,
+        setSelectedOccurrenceId,
+        setSelectedInstanceId,
+        isFinal,
+        makeOccurrenceFinal,
+    } = useContext(AnnotationContext);
+
+    const onChangeSentiment = (occurrence: TagInstanceInfo) => (val: ETagSentiment) => {
+        changeSentiment(occurrence, val);
+    };
+
+    /**
+     * Removes an occurrence of this annotation from the context.
+     * @param occurrence The occurrence that should be removed.
+     */
+    const onDeleteOccurrence = (occurrence: TagInstanceInfo) => (e: any) => {
+        deleteOccurrence(occurrence);
+    };
+
+    const onChangeNote =
+        (occurrence: TagInstanceInfo) => (e: ChangeEvent<HTMLTextAreaElement>) => {
+            changeNote(occurrence, e.currentTarget.value);
+        };
+
+    function getBackgroundColor(): string {
+        if (selectedOccurrenceId === props.occurrence.occurenceId) {
+            // highlighted occurrence
+            return COLOR_HIGHLIGHTED_OCCURRENCE;
+        }
+
+        if (isFinal) {
+            const allAccepted =
+                props.tag.occurrences.filter((o) => !o.isFinal).length === 0;
+            if (allAccepted) {
+                return COLOR_OCCURRENCE_ACCEPTED;
+            }
+        }
+
+        return 'white';
+    }
+
+    return (
+        <Container
+            className="shadow-sm"
+            style={{
+                backgroundColor: getBackgroundColor(),
+            }}
+        >
+            <Row className="mb-1 mt-1">
+                <Col>
+                    <Row>
+                        <Col>Pozice: {props.occurrence.position}</Col>
+                        <Col>Délka: {props.occurrence.length}</Col>
+                    </Row>
+                    <Row>
+                        <i title={props.occurrence.selectedText ?? ''}>
+                            {getTextMaxLength(props.occurrence.selectedText ?? '', 35)}
+                        </i>
+                    </Row>
+                    <Row>
+                        <Col className="d-flex align-items-center" sm="4">
+                            Poznámka:
+                        </Col>
+                        <Col>
+                            <Input.TextArea
+                                defaultValue={props.occurrence.note ?? ''}
+                                onBlur={onChangeNote(props.occurrence)}
+                                rows={1}
+                            />
+                        </Col>
+                    </Row>
+                    {props.occurrence.sentiment && (
+                        <Row>
+                            <Col className="d-flex align-items-center" sm="4">
+                                Sentiment:
+                            </Col>
+                            <Col>
+                                <Select
+                                    defaultValue={props.occurrence.sentiment}
+                                    style={{ width: '100%' }}
+                                    onChange={onChangeSentiment(props.occurrence)}
+                                >
+                                    <Option value={ETagSentiment.Positive}>
+                                        <span style={{ color: 'green' }}>Pozitivní</span>
+                                    </Option>
+                                    <Option value={ETagSentiment.Neutral}>
+                                        Neutrální
+                                    </Option>
+                                    <Option value={ETagSentiment.Negative}>
+                                        <span
+                                            style={{
+                                                color: '#ff4d4f',
+                                            }}
+                                        >
+                                            Negativní
+                                        </span>
+                                    </Option>
+                                </Select>
+                            </Col>
+                        </Row>
+                    )}
+                </Col>
+                <Col
+                    sm="auto"
+                    className="d-flex align-items-center flex-column justify-content-sm-evenly"
+                >
+                    <Button
+                        icon={<EyeOutlined />}
+                        onClick={() => {
+                            setSelectedOccurrenceId(props.occurrence.occurenceId ?? null);
+                            setSelectedInstanceId(props.tag.instanceId);
+                        }}
+                        title={'Zvýraznit výskyt'}
+                    />
+                    <Button
+                        icon={<DeleteOutlined />}
+                        onClick={onDeleteOccurrence(props.occurrence)}
+                        danger
+                        className={'mt-1'}
+                    />
+                    <Button
+                        icon={<LikeOutlined />}
+                        title={'Označit jako správné řešení'}
+                        onClick={() => {
+                            ShowConfirm(() => {
+                                makeOccurrenceFinal(props.occurrence);
+                            }, 'označit toto řešení jako správné');
+                        }}
+                        className={'mt-1'}
+                    />
+                </Col>
+            </Row>
+            <Row className="mb-1 mt-1">
+                <Col>
+                    Označili:{' '}
+                    {props.occurrence.users?.map((u) => (
+                        <span
+                            title={u.name + ' ' + u.surname + ' (' + u.username + ')'}
+                            key={props.occurrence.occurenceId + '.' + u.id}
+                            style={{ marginRight: 10 }}
+                        >
+                            {getNameTruncated(u)}
+                        </span>
+                    ))}
+                </Col>
+            </Row>
+        </Container>
+    );
+}
diff --git a/webapp/components/annotation/DocumentAnnotationView.tsx b/webapp/components/annotation/DocumentAnnotationView.tsx
index a611e83..45369c2 100644
--- a/webapp/components/annotation/DocumentAnnotationView.tsx
+++ b/webapp/components/annotation/DocumentAnnotationView.tsx
@@ -6,6 +6,7 @@ import { useContext } from 'react';
 import { AnnotationContext } from '../../contexts/AnnotationContext';
 import parse from 'html-react-parser';
 import {
+    COLOR_HIGHLIGHTED_TAG,
     DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME,
     DOCUMENT_OCCURRENCE_ID_ATTRIBUTE_NAME,
     DOCUMENT_TAG_ID_ATTRIBUTE_NAME,
@@ -58,7 +59,7 @@ export function DocumentAnnotationView() {
         }
 
         if (selectedInstanceId) {
-            css += `span[${DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME}="${selectedInstanceId}"] { background-color: #FCF3CF;  }\n`;
+            css += `span[${DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME}="${selectedInstanceId}"] { background-color: ${COLOR_HIGHLIGHTED_TAG};  }\n`;
         }
         if (selectedOccurrenceId) {
             css += `span[${DOCUMENT_OCCURRENCE_ID_ATTRIBUTE_NAME}="${selectedOccurrenceId}"] { border-bottom: 4px dotted;  }\n`;
diff --git a/webapp/constants.ts b/webapp/constants.ts
index f2671d6..59f66f2 100644
--- a/webapp/constants.ts
+++ b/webapp/constants.ts
@@ -16,6 +16,12 @@ export const COLOR_SUCCESS = 'darkgreen';
 export const COLOR_WARNING = 'darkorange';
 export const COLOR_ERROR = 'indianred';
 
+export const COLOR_HIGHLIGHTED_TAG = '#97BDC4';
+export const COLOR_HIGHLIGHTED_OCCURRENCE = '#D8E1E9';
+
+export const COLOR_ALL_OCCURRENCES_ACCEPTED = '#B6CEB7';
+export const COLOR_OCCURRENCE_ACCEPTED = '#9DBE9E';
+
 export const BORDER_RADIUS = 4;
 
 export const DOCUMENT_TAG_ID_ATTRIBUTE_NAME = 'aswi-tag-id';
diff --git a/webapp/contexts/AnnotationContext.tsx b/webapp/contexts/AnnotationContext.tsx
index e4005bd..39328c3 100644
--- a/webapp/contexts/AnnotationContext.tsx
+++ b/webapp/contexts/AnnotationContext.tsx
@@ -77,6 +77,8 @@ interface IAnnotationContextProvider {
      */
     changeSentiment: (occurrence: TagInstanceInfo, newValue: ETagSentiment) => void;
 
+    makeOccurrenceFinal: (occurrence: TagInstanceInfo) => void;
+
     annotation: AnnotationInfo | null;
     mappedTags: Tag[] | null;
     refreshAnnotation: () => void;
@@ -92,6 +94,8 @@ interface IAnnotationContextProvider {
 
     selectedOccurrenceId: string | null;
     setSelectedOccurrenceId: (newId: string | null) => void;
+
+    isFinal: boolean;
 }
 
 /**
@@ -194,6 +198,11 @@ export const AnnotationContext = createContext<IAnnotationContextProvider>({
     setSelectedOccurrenceId: (newId: string | null) => {
         return;
     },
+
+    isFinal: false,
+    makeOccurrenceFinal: (occurrence: TagInstanceInfo) => {
+        return;
+    },
 });
 
 /**
@@ -282,7 +291,8 @@ const AnnotationProvider = (props: {
             annotationController
                 .annotationAnnotationIdOccurenceIdDelete(
                     props.annotationId,
-                    occurrence.occurenceId ?? ''
+                    occurrence.occurenceId ?? '',
+                    props.isFinal
                 )
                 .then(() => refreshAnnotation());
         }, 'značku');
@@ -397,6 +407,20 @@ const AnnotationProvider = (props: {
         setMappedTags(Array.from(map.values()));
     };
 
+    async function makeOccurrenceFinal(occurrence: TagInstanceInfo) {
+        if (!occurrence?.occurenceId) {
+            return;
+        }
+
+        await annotationController.annotationAnnotationIdOccurenceIdFinalPut(
+            props.annotationId,
+            occurrence.occurenceId,
+            { isFinal: true }
+        );
+
+        await refreshAnnotation();
+    }
+
     async function refreshAnnotation() {
         const data = await annotationController.annotationAnnotationIdGet(
             props.annotationId,
@@ -436,6 +460,8 @@ const AnnotationProvider = (props: {
                 setSelectedInstanceId,
                 selectedOccurrenceId,
                 setSelectedOccurrenceId,
+                isFinal: props.isFinal,
+                makeOccurrenceFinal,
             }}
         >
             {props.children}
