diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index d7263de82..8e8ace690 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -120,6 +120,7 @@ import { RotateConfirmDialogComponent } from './components/common/confirm-dialog import { MergeConfirmDialogComponent } from './components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component' import { SplitConfirmDialogComponent } from './components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component' import { DocumentHistoryComponent } from './components/document-history/document-history.component' +import { DragDropSelectComponent } from './components/common/input/drag-drop-select/drag-drop-select.component' import { airplane, archive, @@ -474,6 +475,7 @@ function initializeApp(settings: SettingsService) { MergeConfirmDialogComponent, SplitConfirmDialogComponent, DocumentHistoryComponent, + DragDropSelectComponent, ], imports: [ BrowserModule, diff --git a/src-ui/src/app/components/admin/settings/settings.component.html b/src-ui/src/app/components/admin/settings/settings.component.html index 0fc744edb..2c6cbe904 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.html +++ b/src-ui/src/app/components/admin/settings/settings.component.html @@ -320,17 +320,14 @@

Views

-
+
+ @@ -374,4 +393,5 @@
+ diff --git a/src-ui/src/app/components/admin/settings/settings.component.spec.ts b/src-ui/src/app/components/admin/settings/settings.component.spec.ts index d53f57b69..2f76ec361 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.spec.ts +++ b/src-ui/src/app/components/admin/settings/settings.component.spec.ts @@ -437,4 +437,11 @@ describe('SettingsComponent', () => { size: 'xl', }) }) + + it('should support reset', () => { + completeSetup() + component.settingsForm.get('themeColor').setValue('#ff0000') + component.reset() + expect(component.settingsForm.get('themeColor').value).toEqual('') + }) }) diff --git a/src-ui/src/app/components/admin/settings/settings.component.ts b/src-ui/src/app/components/admin/settings/settings.component.ts index 33f6949a1..5e59d561d 100644 --- a/src-ui/src/app/components/admin/settings/settings.component.ts +++ b/src-ui/src/app/components/admin/settings/settings.component.ts @@ -26,7 +26,12 @@ import { tap, } from 'rxjs' import { Group } from 'src/app/data/group' -import { SavedView } from 'src/app/data/saved-view' +import { + DASHBOARD_VIEW_TABLE_COLUMNS, + DashboardViewMode, + DashboardViewTableColumn, + SavedView, +} from 'src/app/data/saved-view' import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { User } from 'src/app/data/user' import { DocumentListViewService } from 'src/app/services/document-list-view.service' @@ -73,8 +78,8 @@ export class SettingsComponent extends ComponentWithPermissions implements OnInit, AfterViewInit, OnDestroy, DirtyComponent { - SettingsNavIDs = SettingsNavIDs activeNavID: number + DashboardViewMode = DashboardViewMode savedViewGroup = new FormGroup({}) @@ -110,6 +115,8 @@ export class SettingsComponent }) savedViews: SavedView[] + SettingsNavIDs = SettingsNavIDs + DASHBOARD_VIEW_TABLE_COLUMNS = DASHBOARD_VIEW_TABLE_COLUMNS store: BehaviorSubject storeSub: Subscription @@ -340,6 +347,9 @@ export class SettingsComponent name: view.name, show_on_dashboard: view.show_on_dashboard, show_in_sidebar: view.show_in_sidebar, + dashboard_view_limit: view.dashboard_view_limit, + dashboard_view_mode: view.dashboard_view_mode, + dashboard_view_table_columns: view.dashboard_view_table_columns, } this.savedViewGroup.addControl( view.id.toString(), @@ -348,6 +358,9 @@ export class SettingsComponent name: new FormControl(null), show_on_dashboard: new FormControl(null), show_in_sidebar: new FormControl(null), + dashboard_view_limit: new FormControl(null), + dashboard_view_mode: new FormControl(null), + dashboard_view_table_columns: new FormControl([]), }) ) } @@ -592,6 +605,10 @@ export class SettingsComponent } } + reset() { + this.settingsForm.patchValue(this.store.getValue()) + } + clearThemeColor() { this.settingsForm.get('themeColor').patchValue('') } diff --git a/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.html b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.html new file mode 100644 index 000000000..e0ef64e71 --- /dev/null +++ b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.html @@ -0,0 +1,24 @@ +
+ Selected: +
+ @for (item of selectedItems; track item.id) { +
{{item.name}}
+ } +
+
+
+ +
+ @for (item of unselectedItems; track item.id) { +
{{item.name}}
+ } +
+
diff --git a/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.scss b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.scss new file mode 100644 index 000000000..483c6c592 --- /dev/null +++ b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.scss @@ -0,0 +1,3 @@ +.badge { + cursor: move; +} diff --git a/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.spec.ts b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.spec.ts new file mode 100644 index 000000000..4477c9e92 --- /dev/null +++ b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.spec.ts @@ -0,0 +1,99 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { DragDropModule } from '@angular/cdk/drag-drop' +import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms' +import { DragDropSelectComponent } from './drag-drop-select.component' + +describe('DragDropSelectComponent', () => { + let component: DragDropSelectComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DragDropModule, FormsModule], + declarations: [DragDropSelectComponent], + }).compileComponents() + + fixture = TestBed.createComponent(DragDropSelectComponent) + component = fixture.componentInstance + fixture.debugElement.injector.get(NG_VALUE_ACCESSOR) + fixture.detectChanges() + }) + + it('should update selectedItems when writeValue is called', () => { + const newValue = ['1', '2', '3'] + component.items = [ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + { id: '3', name: 'Item 3' }, + ] + component.writeValue(newValue) + expect(component.selectedItems).toEqual([ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + { id: '3', name: 'Item 3' }, + ]) + }) + + it('should update selectedItems when an item is dropped within selectedList', () => { + component.items = [ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + { id: '3', name: 'Item 3' }, + { id: '4', name: 'Item 4' }, + ] + component.writeValue(['1', '2', '3']) + const event = { + previousContainer: component.selectedList, + container: component.selectedList, + previousIndex: 1, + currentIndex: 2, + } + component.drop(event as any) + expect(component.selectedItems).toEqual([ + { id: '1', name: 'Item 1' }, + { id: '3', name: 'Item 3' }, + { id: '2', name: 'Item 2' }, + ]) + }) + + it('should update selectedItems when an item is dropped from unselectedList to selectedList', () => { + component.items = [ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + { id: '3', name: 'Item 3' }, + ] + component.writeValue(['1', '2']) + const event = { + previousContainer: component.unselectedList, + container: component.selectedList, + previousIndex: 0, + currentIndex: 2, + } + component.drop(event as any) + expect(component.selectedItems).toEqual([ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + { id: '3', name: 'Item 3' }, + ]) + }) + + it('should update selectedItems when an item is dropped from selectedList to unselectedList', () => { + component.items = [ + { id: '1', name: 'Item 1' }, + { id: '2', name: 'Item 2' }, + { id: '3', name: 'Item 3' }, + ] + component.writeValue(['1', '2', '3']) + const event = { + previousContainer: component.selectedList, + container: component.unselectedList, + previousIndex: 1, + currentIndex: 0, + } + component.drop(event as any) + expect(component.selectedItems).toEqual([ + { id: '1', name: 'Item 1' }, + { id: '3', name: 'Item 3' }, + ]) + }) +}) diff --git a/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.ts b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.ts new file mode 100644 index 000000000..8adfbad06 --- /dev/null +++ b/src-ui/src/app/components/common/input/drag-drop-select/drag-drop-select.component.ts @@ -0,0 +1,61 @@ +import { Component, Input, ViewChild, forwardRef } from '@angular/core' +import { NG_VALUE_ACCESSOR } from '@angular/forms' +import { AbstractInputComponent } from '../abstract-input' +import { + CdkDragDrop, + CdkDropList, + moveItemInArray, +} from '@angular/cdk/drag-drop' + +@Component({ + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DragDropSelectComponent), + multi: true, + }, + ], + selector: 'pngx-input-drag-drop-select', + templateUrl: './drag-drop-select.component.html', + styleUrl: './drag-drop-select.component.scss', +}) +export class DragDropSelectComponent extends AbstractInputComponent { + @Input() items: { id: string; name: string }[] = [] + public selectedItems: { id: string; name: string }[] = [] + + @ViewChild('selectedList') selectedList: CdkDropList + @ViewChild('unselectedList') unselectedList: CdkDropList + + get unselectedItems(): { id: string; name: string }[] { + return this.items.filter((i) => !this.selectedItems.includes(i)) + } + + writeValue(newValue: string[]): void { + super.writeValue(newValue) + this.selectedItems = newValue.map((id) => + this.items.find((i) => i.id === id) + ) + } + + public drop(event: CdkDragDrop) { + if ( + event.previousContainer === event.container && + event.container === this.selectedList + ) { + moveItemInArray( + this.selectedItems, + event.previousIndex, + event.currentIndex + ) + } else if (event.container === this.selectedList) { + this.selectedItems.splice( + event.currentIndex, + 0, + this.unselectedItems[event.previousIndex] + ) + } else { + this.selectedItems.splice(event.previousIndex, 1) + } + this.onChange(this.selectedItems.map((i) => i.id)) + } +} 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 cddfd2bf5..6496698dd 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 @@ -13,6 +13,7 @@ import { DashboardViewTableColumn, DashboardViewMode, SavedView, + DASHBOARD_VIEW_TABLE_COLUMNS, } from 'src/app/data/saved-view' import { ConsumerStatusService } from 'src/app/services/consumer-status.service' import { DocumentService } from 'src/app/services/rest/document.service' @@ -223,21 +224,6 @@ export class SavedViewWidgetComponent } 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` - } + return DASHBOARD_VIEW_TABLE_COLUMNS.find((c) => c.id === column)?.name } } diff --git a/src-ui/src/app/data/saved-view.ts b/src-ui/src/app/data/saved-view.ts index ced629507..605b2d934 100644 --- a/src-ui/src/app/data/saved-view.ts +++ b/src-ui/src/app/data/saved-view.ts @@ -16,6 +16,37 @@ export enum DashboardViewTableColumn { STORAGE_PATH = 'storagepath', } +export const DASHBOARD_VIEW_TABLE_COLUMNS = [ + { + id: DashboardViewTableColumn.TITLE, + name: $localize`Title`, + }, + { + id: DashboardViewTableColumn.CREATED, + name: $localize`Created`, + }, + { + id: DashboardViewTableColumn.ADDED, + name: $localize`Added`, + }, + { + id: DashboardViewTableColumn.TAGS, + name: $localize`Tags`, + }, + { + id: DashboardViewTableColumn.CORRESPONDENT, + name: $localize`Correspondent`, + }, + { + id: DashboardViewTableColumn.DOCUMENT_TYPE, + name: $localize`Document type`, + }, + { + id: DashboardViewTableColumn.STORAGE_PATH, + name: $localize`Storage path`, + }, +] + export interface SavedView extends ObjectWithPermissions { name?: string