commit 02818aaf05095aac84d7a1d1fd01835a730417de
Author: Ondřej Váně <onry.vane@gmail.com>
Date:   Tue Apr 7 18:44:25 2020 +0200

    Re #7819: Úprava obrazovky browse
    
    - upravena UI
    - přidána tabulka se stránkováním
    - přidány datum do modelové třídy (datum uploadu a datum původu)

diff --git a/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/IndexController.java b/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/IndexController.java
index ca7928c..43b9423 100644
--- a/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/IndexController.java
+++ b/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/IndexController.java
@@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile;
 import javax.xml.bind.JAXBException;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
 @CrossOrigin(origins = "*", allowedHeaders = "*")
@@ -61,7 +62,7 @@ public class IndexController {
             List<String> filenames = solrService.listAllFiles();
             int id = 0; // todo
             for (String filename: filenames) {
-                response.add(new DocumentResponse(filename, id++));
+                response.add(new DocumentResponse(filename, id++, new Date(), new Date()));
             }
         } catch (IOException e) {
             // todo
diff --git a/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/model/DocumentResponse.java b/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/model/DocumentResponse.java
index a4efbff..5db3d10 100644
--- a/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/model/DocumentResponse.java
+++ b/be/fulltextsearch/src/main/java/cz/zcu/kiv/aswi/fulltextsearch/model/DocumentResponse.java
@@ -1,20 +1,26 @@
 package cz.zcu.kiv.aswi.fulltextsearch.model;
 
+import java.util.Date;
+
 public class DocumentResponse {
 
     private String documentName;
     private int id;
+    private Date originDate;
+    private Date uploadedDate;
 
-    public DocumentResponse(String documentName, int id) {
+    public DocumentResponse(String documentName, int id, Date originDate, Date uploadedDate) {
         this.documentName = documentName;
         this.id = id;
+        this.originDate = originDate;
+        this.uploadedDate = uploadedDate;
     }
 
     public String getDocumentName() {
         return documentName;
     }
 
-    public void setDocumentName(String documentName, int id) {
+    public void setDocumentName(String documentName) {
         this.documentName = documentName;
     }
 
@@ -25,4 +31,20 @@ public class DocumentResponse {
     public void setId(int id) {
         this.id = id;
     }
+
+    public Date getOriginDate() {
+        return originDate;
+    }
+
+    public void setOriginDate(Date originDate) {
+        this.originDate = originDate;
+    }
+
+    public Date getUploadedDate() {
+        return uploadedDate;
+    }
+
+    public void setUploadedDate(Date uploadedDate) {
+        this.uploadedDate = uploadedDate;
+    }
 }
diff --git a/fe/fulltextsearch/src/app/app.module.ts b/fe/fulltextsearch/src/app/app.module.ts
index 0c1460b..cf0ee06 100644
--- a/fe/fulltextsearch/src/app/app.module.ts
+++ b/fe/fulltextsearch/src/app/app.module.ts
@@ -18,6 +18,10 @@ import { HelpComponent } from './components/pages/help/help.component';
 import { MatSelectModule } from '@angular/material/select';
 import { SearchBoxComponent } from './components/shared-components/search-box/search-box.component';
 import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { TableComponent } from './components/pages/browse/table/table.component';
+import { MatTableModule } from '@angular/material/table';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
 
 @NgModule({
   declarations: [
@@ -28,7 +32,8 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
     BrowseComponent,
     UploadComponent,
     HelpComponent,
-    SearchBoxComponent
+    SearchBoxComponent,
+    TableComponent
   ],
   imports: [
     BrowserModule,
@@ -40,7 +45,10 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
     MatButtonModule,
     MatIconModule,
     MatSelectModule,
-    MatProgressBarModule
+    MatProgressBarModule,
+    MatTableModule,
+    MatPaginatorModule,
+    MatSortModule
   ],
   providers: [],
   bootstrap: [AppComponent]
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/browse.component.css b/fe/fulltextsearch/src/app/components/pages/browse/browse.component.css
index e535a99..645320c 100644
--- a/fe/fulltextsearch/src/app/components/pages/browse/browse.component.css
+++ b/fe/fulltextsearch/src/app/components/pages/browse/browse.component.css
@@ -1,9 +1,8 @@
-h1 {
-  font-size: xx-large;
+.text-box{
+  width: 80%;
+  margin: 20px auto;
 }
 
-.text-box {
-  margin: auto;
-  width: 80%;
-  padding: 10px;
+.card-header {
+  margin-bottom: 0;
 }
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/browse.component.html b/fe/fulltextsearch/src/app/components/pages/browse/browse.component.html
index b089fae..37cc136 100644
--- a/fe/fulltextsearch/src/app/components/pages/browse/browse.component.html
+++ b/fe/fulltextsearch/src/app/components/pages/browse/browse.component.html
@@ -1,13 +1,9 @@
-<div class="text-box">
+<div class="text-box mat-elevation-z8">
   <div class="card">
     <h1 class="card-header">All documents</h1>
-    <div class="card-body">
-      <ul class="list-group">
-        <li class="list-group-item" *ngFor="let documentResponse of documents">
-          <b>Id: </b>{{documentResponse.id}}
-          <b>Document name: </b>{{documentResponse.documentName}}
-        </li>
-      </ul>
-    </div>
+      <div>
+        <app-table-browse></app-table-browse>
+      </div>
   </div>
 </div>
+
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/table/table-datasource.ts b/fe/fulltextsearch/src/app/components/pages/browse/table/table-datasource.ts
new file mode 100644
index 0000000..ac2ea55
--- /dev/null
+++ b/fe/fulltextsearch/src/app/components/pages/browse/table/table-datasource.ts
@@ -0,0 +1,78 @@
+import {DataSource} from '@angular/cdk/collections';
+import {MatPaginator} from '@angular/material/paginator';
+import {MatSort} from '@angular/material/sort';
+import {map} from 'rxjs/operators';
+import {merge, Observable, of as observableOf} from 'rxjs';
+import {DocumentResponse} from '../../../../model/DocumentResponse';
+
+/**
+ * Data source for the Table view. This class should
+ * encapsulate all logic for fetching and manipulating the displayed data
+ * (including sorting, pagination, and filtering).
+ */
+export class TableDataSource extends DataSource<DocumentResponse> {
+  data: DocumentResponse[];
+  paginator: MatPaginator;
+  sort: MatSort;
+
+  constructor() {
+    super();
+  }
+
+  /**
+   * Connect this data source to the table. The table will only update when
+   * the returned stream emits new items.
+   * @returns A stream of the items to be rendered.
+   */
+  connect(): Observable<DocumentResponse[]> {
+    // Combine everything that affects the rendered data into one update
+    // stream for the data-table to consume.
+    const dataMutations = [
+      observableOf(this.data),
+      this.paginator.page,
+      this.sort.sortChange
+    ];
+    return merge(...dataMutations).pipe(map(() => {
+      return this.getPagedData(this.getSortedData([...this.data]));
+    }));
+  }
+
+  /**
+   *  Called when the table is being destroyed. Use this function, to clean up
+   * any open connections or free any held resources that were set up during connect.
+   */
+  disconnect() {}
+
+  /**
+   * Paginate the data (client-side). If you're using server-side pagination,
+   * this would be replaced by requesting the appropriate data from the server.
+   */
+  private getPagedData(data: DocumentResponse[]) {
+    const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
+    return data.splice(startIndex, this.paginator.pageSize);
+  }
+
+  /**
+   * Sort the data (client-side). If you're using server-side sorting,
+   * this would be replaced by requesting the appropriate data from the server.
+   */
+  private getSortedData(data: DocumentResponse[]) {
+    if (!this.sort.active || this.sort.direction === '') {
+      return data;
+    }
+
+    return data.sort((a, b) => {
+      const isAsc = this.sort.direction === 'asc';
+      switch (this.sort.active) {
+        case 'name': return compare(a.documentName, b.documentName, isAsc);
+        case 'id': return compare(+a.id, +b.id, isAsc);
+        default: return 0;
+      }
+    });
+  }
+}
+
+/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
+function compare(a: string | number, b: string | number, isAsc: boolean) {
+  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.css b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.css
new file mode 100644
index 0000000..5050fb6
--- /dev/null
+++ b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.css
@@ -0,0 +1,3 @@
+.full-width-table {
+  width: 100%;
+}
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.html b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.html
new file mode 100644
index 0000000..48d9a1b
--- /dev/null
+++ b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.html
@@ -0,0 +1,41 @@
+<div>
+  <table mat-table class="full-width-table" matSort aria-label="Elements">
+    <!-- Id Column -->
+    <ng-container matColumnDef="id">
+      <th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
+      <td mat-cell *matCellDef="let row">{{row.id}}</td>
+    </ng-container>
+
+    <!-- Name Column -->
+    <ng-container matColumnDef="documentName">
+      <th mat-header-cell *matHeaderCellDef mat-sort-header>Document name</th>
+      <td mat-cell *matCellDef="let row">{{row.documentName}}</td>
+    </ng-container>
+
+    <!-- Test Column -->
+    <ng-container matColumnDef="originDate">
+      <th mat-header-cell *matHeaderCellDef mat-sort-header>Origin date</th>
+      <td mat-cell *matCellDef="let row">{{row.originDate | date:'d. M. yyyy'}}</td>
+    </ng-container>
+
+    <!-- Test Column -->
+    <ng-container matColumnDef="uploadedDate">
+      <th mat-header-cell *matHeaderCellDef mat-sort-header>Uploaded date</th>
+      <td mat-cell *matCellDef="let row">{{row.uploadedDate | date:'d. M. yyyy'}}</td>
+    </ng-container>
+
+    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
+  </table>
+
+  <div class="row justify-content-center align-items-center">
+
+    <mat-paginator #paginator
+                   [length]="dataSource?.data.length"
+                   [pageIndex]="0"
+                   [pageSize]="10"
+                   [pageSizeOptions]="[5, 10, 25]">
+    </mat-paginator>
+  </div>
+
+</div>
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.spec.ts b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.spec.ts
new file mode 100644
index 0000000..2179eed
--- /dev/null
+++ b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.spec.ts
@@ -0,0 +1,34 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTableModule } from '@angular/material/table';
+
+import { TableComponent } from './table.component';
+
+describe('TableComponent', () => {
+  let component: TableComponent;
+  let fixture: ComponentFixture<TableComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ TableComponent ],
+      imports: [
+        NoopAnimationsModule,
+        MatPaginatorModule,
+        MatSortModule,
+        MatTableModule,
+      ]
+    }).compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TableComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should compile', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.ts b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.ts
new file mode 100644
index 0000000..fe826eb
--- /dev/null
+++ b/fe/fulltextsearch/src/app/components/pages/browse/table/table.component.ts
@@ -0,0 +1,37 @@
+import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import { MatTable } from '@angular/material/table';
+import { TableDataSource } from './table-datasource';
+import { DocumentResponse } from '../../../../model/DocumentResponse';
+import {QueryService} from '../../../../services/query.service';
+
+@Component({
+  selector: 'app-table-browse',
+  templateUrl: './table.component.html',
+  styleUrls: ['./table.component.css']
+})
+export class TableComponent implements AfterViewInit, OnInit {
+  @ViewChild(MatPaginator) paginator: MatPaginator;
+  @ViewChild(MatSort) sort: MatSort;
+  @ViewChild(MatTable) table: MatTable<DocumentResponse>;
+  dataSource: TableDataSource;
+
+  /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
+  displayedColumns = ['id', 'documentName', 'originDate', 'uploadedDate'];
+
+  constructor( private queryService: QueryService ) { }
+
+  ngOnInit() {
+    this.queryService.getDocuments().subscribe( documents => {
+      this.dataSource.data = documents;
+    });
+    this.dataSource = new TableDataSource();
+  }
+
+  ngAfterViewInit() {
+    this.dataSource.sort = this.sort;
+    this.dataSource.paginator = this.paginator;
+    this.table.dataSource = this.dataSource;
+  }
+}
diff --git a/fe/fulltextsearch/src/app/model/DocumentResponse.ts b/fe/fulltextsearch/src/app/model/DocumentResponse.ts
index cb46dfb..30b3f4f 100644
--- a/fe/fulltextsearch/src/app/model/DocumentResponse.ts
+++ b/fe/fulltextsearch/src/app/model/DocumentResponse.ts
@@ -1,4 +1,6 @@
 export class DocumentResponse {
   public id: number;
-  documentName: string;
+  public documentName: string;
+  public originDate: Date;
+  public uploadedDate: Date;
 }
diff --git a/fe/fulltextsearch/src/theme.css b/fe/fulltextsearch/src/theme.css
index 7f40ec6..4b092b8 100644
--- a/fe/fulltextsearch/src/theme.css
+++ b/fe/fulltextsearch/src/theme.css
@@ -5,4 +5,6 @@
   --search-text-color: #ffffff;
   --search-background-color: #ffffff;
   --navbar-text-color: #ffffff;
+  --grey-color: #DFE2E6;
+  --black-color: #000000;
 }
