From 38e79aa1d9d7551bb5381931aaba4dd18f6cea33 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 16 Apr 2024 20:19:58 -0700 Subject: [PATCH] Customizable dashboard views --- .../e2e/dashboard/requests/api-dashboard1.har | 2 +- .../e2e/dashboard/requests/api-dashboard2.har | 2 +- .../e2e/dashboard/requests/api-dashboard3.har | 2 +- .../e2e/dashboard/requests/api-dashboard4.har | 2 +- .../saved-view-widget.component.html | 109 +++++++++----- .../saved-view-widget.component.scss | 49 ++++++- .../saved-view-widget.component.spec.ts | 134 +++++++++++++++++- .../saved-view-widget.component.ts | 107 +++++++++++--- .../widget-frame/widget-frame.component.html | 4 +- src-ui/src/app/data/saved-view.ts | 21 +++ ...7_savedview_dasboard_view_mode_and_more.py | 52 +++++++ src/documents/models.py | 33 +++++ src/documents/serialisers.py | 7 + 13 files changed, 452 insertions(+), 72 deletions(-) create mode 100644 src/documents/migrations/1047_savedview_dasboard_view_mode_and_more.py diff --git a/src-ui/e2e/dashboard/requests/api-dashboard1.har b/src-ui/e2e/dashboard/requests/api-dashboard1.har index 3e0829c2f..0cc2171f6 100644 --- a/src-ui/e2e/dashboard/requests/api-dashboard1.har +++ b/src-ui/e2e/dashboard/requests/api-dashboard1.har @@ -124,7 +124,7 @@ "content": { "size": -1, "mimeType": "application/json", - "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}" + "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}" }, "headersSize": -1, "bodySize": -1, diff --git a/src-ui/e2e/dashboard/requests/api-dashboard2.har b/src-ui/e2e/dashboard/requests/api-dashboard2.har index 2436a6272..f3b4e9e8b 100644 --- a/src-ui/e2e/dashboard/requests/api-dashboard2.har +++ b/src-ui/e2e/dashboard/requests/api-dashboard2.har @@ -124,7 +124,7 @@ "content": { "size": -1, "mimeType": "application/json", - "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}" + "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}" }, "headersSize": -1, "bodySize": -1, diff --git a/src-ui/e2e/dashboard/requests/api-dashboard3.har b/src-ui/e2e/dashboard/requests/api-dashboard3.har index 328c9db6e..8e3c59b55 100644 --- a/src-ui/e2e/dashboard/requests/api-dashboard3.har +++ b/src-ui/e2e/dashboard/requests/api-dashboard3.har @@ -124,7 +124,7 @@ "content": { "size": -1, "mimeType": "application/json", - "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}" + "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}" }, "headersSize": -1, "bodySize": -1, diff --git a/src-ui/e2e/dashboard/requests/api-dashboard4.har b/src-ui/e2e/dashboard/requests/api-dashboard4.har index ca0101d59..53a835307 100644 --- a/src-ui/e2e/dashboard/requests/api-dashboard4.har +++ b/src-ui/e2e/dashboard/requests/api-dashboard4.har @@ -124,7 +124,7 @@ "content": { "size": -1, "mimeType": "application/json", - "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true}]}" + "text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"dashboard_view_limit\":10,\"dashboard_view_mode\":\"table\",\"dashboard_view_table_columns\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}" }, "headersSize": -1, "bodySize": -1, diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html index de46991d2..b8e02bfac 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html @@ -9,58 +9,89 @@ Show all } - @if (documents.length) { - + @if (documents.length && savedView.dashboard_view_mode === DashboardViewMode.TABLE) { +
- - - @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) { - - } - @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) { - - } @else { - + @for (column of savedView.dashboard_view_table_columns; track column; let i = $index) { + @if (columnIsVisible(column)) { + + } } @for (doc of documents; track doc) { - - - @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag)) { - - } - } -
- - - - - - - - - -
- + } }
CreatedTitleTagsCorrespondent + {{ getColumnTitle(column) }} +
{{doc.created_date | customDate}} - {{doc.title | documentTitle}} - - @for (t of doc.tags$ | async; track t) { - - } - - @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent) && doc.correspondent !== null) { - {{(doc.correspondent$ | async)?.name}} + @for (column of savedView.dashboard_view_table_columns; track column; let i = $index) { + @if (columnIsVisible(column)) { + + @switch (column) { + @case (DashboardViewTableColumn.ADDED) { + {{doc.added | customDate}} + } + @case (DashboardViewTableColumn.CREATED) { + {{doc.created_date | customDate}} + } + @case (DashboardViewTableColumn.TITLE) { + {{doc.title | documentTitle}} + } + @case (DashboardViewTableColumn.CORRESPONDENT) { + @if (doc.correspondent) { + {{(doc.correspondent$ | async)?.name}} + } + } + @case (DashboardViewTableColumn.TAGS) { + @for (t of doc.tags$ | async; track t) { + + } + } + @case (DashboardViewTableColumn.DOCUMENT_TYPE) { + @if (doc.document_type) { + {{(doc.document_type$ | async)?.name}} + } + } + @case (DashboardViewTableColumn.STORAGE_PATH) { + @if (doc.storage_path) { + {{(doc.storage_path$ | async)?.name}} + } + } + } + @if (i === savedView.dashboard_view_table_columns.length - 1) { +
+ + + + + + + + + +
+ } +
+ } @else if (documents.length && savedView.dashboard_view_mode === DashboardViewMode.SMALL_CARDS) { +
+ @for (d of documents; track d.id) { + + } +
} @else {

No documents

} diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.scss b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.scss index bf1894b48..8c445f18e 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.scss +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.scss @@ -3,10 +3,9 @@ table { table-layout: fixed; } -th:first-child { - width: 25%; - @media (min-width: 768px) { - width: 15%; +@media (min-width: 768px) { + th.w-25 { + width: 15% !important; } } @@ -30,3 +29,45 @@ td.py-3 { padding-top: 0.75em !important; padding-bottom: 0.75em !important; } + +$paperless-card-breakpoints: ( + // 0: 2, // xs is manual for slim-sidebar + 768px: 2, //md + 992px: 2, //lg + 1200px: 3, //xl + 1600px: 4, + 1800px: 5, + 2000px: 6 +); + +.row-cols-paperless-cards { + // xs, we dont want in .col-slim block + > * { + flex: 0 0 auto; + width: calc(100% / 2); + } + + @each $width, $n_cols in $paperless-card-breakpoints { + @media(min-width: $width) { + > * { + flex: 0 0 auto; + width: calc(100% / $n-cols); + } + } + } +} + +::ng-deep .col-slim .row-cols-paperless-cards { + @each $width, $n_cols in $paperless-card-breakpoints { + @media(min-width: $width) { + > * { + flex: 0 0 auto; + width: calc(100% / ($n-cols + 1)) !important; + } + } + } +} + +::ng-deep .document-card-check { + display: none !important; // override for dashboard +} diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts index 545f5696b..2ace024a1 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts @@ -11,8 +11,17 @@ import { RouterTestingModule } from '@angular/router/testing' import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { of, Subject } from 'rxjs' import { routes } from 'src/app/app-routing.module' -import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type' -import { SavedView } from 'src/app/data/saved-view' +import { + FILTER_CORRESPONDENT, + FILTER_DOCUMENT_TYPE, + FILTER_HAS_TAGS_ALL, + FILTER_STORAGE_PATH, +} from 'src/app/data/filter-rule-type' +import { + DashboardViewMode, + DashboardViewTableColumn, + SavedView, +} from 'src/app/data/saved-view' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' @@ -45,6 +54,14 @@ const savedView: SavedView = { value: '1,2', }, ], + dashboard_view_limit: 20, + dashboard_view_mode: DashboardViewMode.TABLE, + dashboard_view_table_columns: [ + DashboardViewTableColumn.CREATED, + DashboardViewTableColumn.TITLE, + DashboardViewTableColumn.TAGS, + DashboardViewTableColumn.CORRESPONDENT, + ], } const documentResults = [ @@ -170,7 +187,7 @@ describe('SavedViewWidgetComponent', () => { component.ngOnInit() expect(listAllSpy).toHaveBeenCalledWith( 1, - 10, + 20, savedView.sort_field, savedView.sort_reverse, savedView.filter_rules, @@ -204,11 +221,120 @@ describe('SavedViewWidgetComponent', () => { }) }) + it('should navigate to document', () => { + const routerSpy = jest.spyOn(router, 'navigate') + component.openDocumentDetail(documentResults[0]) + expect(routerSpy).toHaveBeenCalledWith(['documents', documentResults[0].id]) + }) + it('should navigate via quickfilter on click tag', () => { const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') - component.clickTag({ id: 11, name: 'Tag11' }, new MouseEvent('click')) + component.clickTag(11, new MouseEvent('click')) expect(qfSpy).toHaveBeenCalledWith([ { rule_type: FILTER_HAS_TAGS_ALL, value: '11' }, ]) + component.clickTag(11) // coverage + }) + + it('should navigate via quickfilter on click correspondent', () => { + const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') + component.clickCorrespondent(11, new MouseEvent('click')) + expect(qfSpy).toHaveBeenCalledWith([ + { rule_type: FILTER_CORRESPONDENT, value: '11' }, + ]) + component.clickCorrespondent(11) // coverage + }) + + it('should navigate via quickfilter on click doc type', () => { + const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') + component.clickDocType(11, new MouseEvent('click')) + expect(qfSpy).toHaveBeenCalledWith([ + { rule_type: FILTER_DOCUMENT_TYPE, value: '11' }, + ]) + component.clickDocType(11) // coverage + }) + + it('should navigate via quickfilter on click storage path', () => { + const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') + component.clickStoragePath(11, new MouseEvent('click')) + expect(qfSpy).toHaveBeenCalledWith([ + { rule_type: FILTER_STORAGE_PATH, value: '11' }, + ]) + component.clickStoragePath(11) // coverage + }) + + it('should get correct column title', () => { + expect(component.getColumnTitle(DashboardViewTableColumn.TITLE)).toEqual( + 'Title' + ) + expect(component.getColumnTitle(DashboardViewTableColumn.CREATED)).toEqual( + 'Created' + ) + expect(component.getColumnTitle(DashboardViewTableColumn.ADDED)).toEqual( + 'Added' + ) + expect(component.getColumnTitle(DashboardViewTableColumn.TAGS)).toEqual( + 'Tags' + ) + expect( + component.getColumnTitle(DashboardViewTableColumn.CORRESPONDENT) + ).toEqual('Correspondent') + expect( + component.getColumnTitle(DashboardViewTableColumn.DOCUMENT_TYPE) + ).toEqual('Document type') + expect( + component.getColumnTitle(DashboardViewTableColumn.STORAGE_PATH) + ).toEqual('Storage path') + }) + + it('should check if column is visible including permissions', () => { + expect( + component.columnIsVisible(DashboardViewTableColumn.TITLE) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.CREATED) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.ADDED) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.TAGS) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.CORRESPONDENT) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.DOCUMENT_TYPE) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.STORAGE_PATH) + ).toBeTruthy() + + jest + .spyOn(component.permissionsService, 'currentUserCan') + .mockReturnValue(false) + expect( + component.columnIsVisible(DashboardViewTableColumn.TITLE) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.CREATED) + ).toBeTruthy() + expect( + component.columnIsVisible(DashboardViewTableColumn.ADDED) + ).toBeTruthy() + expect(component.columnIsVisible(DashboardViewTableColumn.TAGS)).toBeFalsy() + expect( + component.columnIsVisible(DashboardViewTableColumn.CORRESPONDENT) + ).toBeFalsy() + expect( + component.columnIsVisible(DashboardViewTableColumn.DOCUMENT_TYPE) + ).toBeFalsy() + expect( + component.columnIsVisible(DashboardViewTableColumn.STORAGE_PATH) + ).toBeFalsy() + + expect( + component.columnIsVisible('unknown' as DashboardViewTableColumn) + ).toBeFalsy() // coverage }) }) diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts index c81ea5484..cddfd2bf5 100644 --- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts +++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts @@ -6,23 +6,31 @@ import { QueryList, ViewChildren, } from '@angular/core' -import { Params, Router } from '@angular/router' +import { Router } from '@angular/router' import { Subject, takeUntil } from 'rxjs' import { Document } from 'src/app/data/document' -import { SavedView } from 'src/app/data/saved-view' +import { + DashboardViewTableColumn, + DashboardViewMode, + SavedView, +} from 'src/app/data/saved-view' import { ConsumerStatusService } from 'src/app/services/consumer-status.service' import { DocumentService } from 'src/app/services/rest/document.service' -import { Tag } from 'src/app/data/tag' import { FILTER_CORRESPONDENT, + FILTER_DOCUMENT_TYPE, FILTER_HAS_TAGS_ALL, + FILTER_STORAGE_PATH, } from 'src/app/data/filter-rule-type' import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component' import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' -import { queryParamsFromFilterRules } from 'src/app/utils/query-params' -import { PermissionsService } from 'src/app/services/permissions.service' +import { + PermissionAction, + PermissionType, + PermissionsService, +} from 'src/app/services/permissions.service' @Component({ selector: 'pngx-saved-view-widget', @@ -33,6 +41,9 @@ export class SavedViewWidgetComponent extends ComponentWithPermissions implements OnInit, OnDestroy { + public DashboardViewMode = DashboardViewMode + public DashboardViewTableColumn = DashboardViewTableColumn + loading: boolean = true constructor( @@ -80,7 +91,7 @@ export class SavedViewWidgetComponent this.documentService .listFiltered( 1, - 10, + this.savedView.dashboard_view_limit, this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules, @@ -103,15 +114,46 @@ export class SavedViewWidgetComponent } } - clickTag(tag: Tag, event: MouseEvent) { - event.preventDefault() - event.stopImmediatePropagation() + clickTag(tagID: number, event: MouseEvent = null) { + event?.preventDefault() + event?.stopImmediatePropagation() this.list.quickFilter([ - { rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() }, + { rule_type: FILTER_HAS_TAGS_ALL, value: tagID.toString() }, ]) } + clickCorrespondent(correspondentId: number, event: MouseEvent = null) { + event?.preventDefault() + event?.stopImmediatePropagation() + + this.list.quickFilter([ + { rule_type: FILTER_CORRESPONDENT, value: correspondentId.toString() }, + ]) + } + + clickDocType(docTypeId: number, event: MouseEvent = null) { + event?.preventDefault() + event?.stopImmediatePropagation() + + this.list.quickFilter([ + { rule_type: FILTER_DOCUMENT_TYPE, value: docTypeId.toString() }, + ]) + } + + clickStoragePath(storagePathId: number, event: MouseEvent = null) { + event?.preventDefault() + event?.stopImmediatePropagation() + + this.list.quickFilter([ + { rule_type: FILTER_STORAGE_PATH, value: storagePathId.toString() }, + ]) + } + + openDocumentDetail(document: Document) { + this.router.navigate(['documents', document.id]) + } + getPreviewUrl(document: Document): string { return this.documentService.getPreviewUrl(document.id) } @@ -161,14 +203,41 @@ export class SavedViewWidgetComponent }, 300) } - getCorrespondentQueryParams(correspondentId: number): Params { - return correspondentId !== undefined - ? queryParamsFromFilterRules([ - { - rule_type: FILTER_CORRESPONDENT, - value: correspondentId.toString(), - }, - ]) - : null + public columnIsVisible(column: DashboardViewTableColumn): boolean { + if ( + [ + DashboardViewTableColumn.TITLE, + DashboardViewTableColumn.CREATED, + DashboardViewTableColumn.ADDED, + ].includes(column) + ) { + return true + } else { + const type: PermissionType = Object.values(PermissionType).find((t) => + t.includes(column) + ) + return type + ? this.permissionsService.currentUserCan(PermissionAction.View, type) + : false + } + } + + public getColumnTitle(column: DashboardViewTableColumn): string { + switch (column) { + case DashboardViewTableColumn.TITLE: + return $localize`Title` + case DashboardViewTableColumn.CREATED: + return $localize`Created` + case DashboardViewTableColumn.ADDED: + return $localize`Added` + case DashboardViewTableColumn.TAGS: + return $localize`Tags` + case DashboardViewTableColumn.CORRESPONDENT: + return $localize`Correspondent` + case DashboardViewTableColumn.DOCUMENT_TYPE: + return $localize`Document type` + case DashboardViewTableColumn.STORAGE_PATH: + return $localize`Storage path` + } } } diff --git a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html index 49af71b08..b64d5e567 100644 --- a/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html +++ b/src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html @@ -13,11 +13,11 @@
Loading...
} - +
- +
diff --git a/src-ui/src/app/data/saved-view.ts b/src-ui/src/app/data/saved-view.ts index b2941bb05..ced629507 100644 --- a/src-ui/src/app/data/saved-view.ts +++ b/src-ui/src/app/data/saved-view.ts @@ -1,6 +1,21 @@ import { FilterRule } from './filter-rule' import { ObjectWithPermissions } from './object-with-permissions' +export enum DashboardViewMode { + TABLE = 'table', + SMALL_CARDS = 'small_cards', +} + +export enum DashboardViewTableColumn { + TITLE = 'title', + CREATED = 'created', + ADDED = 'added', + TAGS = 'tag', + CORRESPONDENT = 'correspondent', + DOCUMENT_TYPE = 'documenttype', + STORAGE_PATH = 'storagepath', +} + export interface SavedView extends ObjectWithPermissions { name?: string @@ -13,4 +28,10 @@ export interface SavedView extends ObjectWithPermissions { sort_reverse: boolean filter_rules: FilterRule[] + + dashboard_view_limit?: number + + dashboard_view_mode?: DashboardViewMode + + dashboard_view_table_columns?: DashboardViewTableColumn[] } diff --git a/src/documents/migrations/1047_savedview_dasboard_view_mode_and_more.py b/src/documents/migrations/1047_savedview_dasboard_view_mode_and_more.py new file mode 100644 index 000000000..0cc64f286 --- /dev/null +++ b/src/documents/migrations/1047_savedview_dasboard_view_mode_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.11 on 2024-04-16 18:35 + +import django.core.validators +import multiselectfield.db.fields +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + dependencies = [ + ("documents", "1046_workflowaction_remove_all_correspondents_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="savedview", + name="dashboard_view_mode", + field=models.CharField( + choices=[("table", "Table"), ("small_cards", "Small Cards")], + default="table", + max_length=128, + verbose_name="Dashboard view display mode", + ), + ), + migrations.AddField( + model_name="savedview", + name="dashboard_view_limit", + field=models.PositiveIntegerField( + default=10, + validators=[django.core.validators.MinValueValidator(1)], + verbose_name="Dashboard view limit", + ), + ), + migrations.AddField( + model_name="savedview", + name="dashboard_view_table_columns", + field=multiselectfield.db.fields.MultiSelectField( + choices=[ + ("title", "Title"), + ("created", "Created"), + ("added", "Added"), + ("tag", "Tags"), + ("documenttype", "Document Type"), + ("correspondent", "Correspondent"), + ("storagepath", "Storage Path"), + ], + default="created,title,tags,correspondent", + max_length=128, + ), + ), + ] diff --git a/src/documents/models.py b/src/documents/models.py index 5cb35a8f7..29eba9822 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -394,6 +394,19 @@ class Log(models.Model): class SavedView(ModelWithOwner): + class DashboardViewDisplayMode(models.TextChoices): + TABLE = ("table", _("Table")) + SMALL_CARDS = ("small_cards", _("Small Cards")) + + class DashboardViewTableColumns(models.TextChoices): + TITLE = ("title", _("Title")) + CREATED = ("created", _("Created")) + ADDED = ("added", _("Added")) + TAGS = ("tag"), _("Tags") + DOCUMENT_TYPE = ("documenttype", _("Document Type")) + CORRESPONDENT = ("correspondent", _("Correspondent")) + STORAGE_PATH = ("storagepath", _("Storage Path")) + name = models.CharField(_("name"), max_length=128) show_on_dashboard = models.BooleanField( @@ -411,6 +424,26 @@ class SavedView(ModelWithOwner): ) sort_reverse = models.BooleanField(_("sort reverse"), default=False) + dashboard_view_limit = models.PositiveIntegerField( + _("Dashboard view limit"), + default=10, + validators=[MinValueValidator(1)], + ) + + dashboard_view_mode = models.CharField( + max_length=128, + verbose_name=_("Dashboard view display mode"), + choices=DashboardViewDisplayMode.choices, + default=DashboardViewDisplayMode.TABLE, + ) + + dashboard_view_table_columns = MultiSelectField( + max_length=128, + verbose_name=_("Dashboard view table display columns"), + choices=DashboardViewTableColumns.choices, + default=f"{DashboardViewTableColumns.CREATED},{DashboardViewTableColumns.TITLE},{DashboardViewTableColumns.TAGS},{DashboardViewTableColumns.CORRESPONDENT}", + ) + class Meta: ordering = ("name",) verbose_name = _("saved view") diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index c7e86a7bf..199aa6493 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -804,6 +804,10 @@ class SavedViewFilterRuleSerializer(serializers.ModelSerializer): class SavedViewSerializer(OwnedObjectSerializer): filter_rules = SavedViewFilterRuleSerializer(many=True) + dashboard_view_table_columns = fields.MultipleChoiceField( + choices=SavedView.DashboardViewTableColumns.choices, + required=False, + ) class Meta: model = SavedView @@ -815,6 +819,9 @@ class SavedViewSerializer(OwnedObjectSerializer): "sort_field", "sort_reverse", "filter_rules", + "dashboard_view_limit", + "dashboard_view_mode", + "dashboard_view_table_columns", "owner", "permissions", "user_can_change",