From 4fd6969d01914f7f5010826965c8de93df72c3a3 Mon Sep 17 00:00:00 2001 From: Borys91 Date: Fri, 26 Apr 2024 17:42:58 +0300 Subject: [PATCH] add selection of projects --- .../issue-filters.component.html | 6 +- .../issue-filters.component.scss | 13 ++++ .../issue-filters/issue-filters.component.ts | 10 +-- .../project-details.component.ts | 14 ++-- .../projects-table.component.html | 21 ++++-- .../projects-table.component.scss | 6 ++ .../projects-table.component.ts | 75 +++++++++++++++---- .../top-menu/top-menu.component.html | 3 + .../top-menu/top-menu.component.scss | 4 + .../components/top-menu/top-menu.component.ts | 14 +++- .../topic-detail/topic-detail.component.html | 10 ++- .../src/app/services/issue-filter.service.ts | 15 ++-- .../selected-project-messenger.service.ts | 17 +++++ .../src/app/services/users.service.ts | 30 ++++++-- 14 files changed, 186 insertions(+), 52 deletions(-) create mode 100644 src/ipa-bcfier-ui/src/app/services/selected-project-messenger.service.ts diff --git a/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.html b/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.html index a8b2b259..098344d1 100644 --- a/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.html +++ b/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.html @@ -18,9 +18,9 @@ Responsible - - @for (user of (users$ | async); track user) { - {{ user }} + + @for (user of (users$ | async); track user.id) { + {{ user.identifier }} } diff --git a/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.scss b/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.scss index fd98ff39..e7a97335 100644 --- a/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.scss +++ b/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.scss @@ -15,3 +15,16 @@ mat-form-field { button { margin: 5px; } + +::ng-deep .panel-bg.mat-mdc-select-panel { + background: black; + opacity: 0.85; +} + +::ng-deep .mat-pseudo-checkbox-full { + border-color: var(--primary-color) !important; +} + +mat-option { + color: var(--primary-color); +} diff --git a/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.ts b/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.ts index 452acfb8..2785232c 100644 --- a/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.ts +++ b/src/ipa-bcfier-ui/src/app/components/issue-filters/issue-filters.component.ts @@ -21,11 +21,12 @@ import { } from '@angular/forms'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { provideNativeDateAdapter } from '@angular/material/core'; +import { ProjectUserGet } from '../../generated-client/generated-client'; export interface IFilters { status: FormControl; type: FormControl; - users: FormControl; + users: FormControl; issueRange: FormGroup<{ start: FormControl; end: FormControl; @@ -69,11 +70,10 @@ export class IssueFiltersComponent { }) issueTypes$!: Observable>; - //TODO replace type any @Input({ required: true, }) - users$!: Observable; + users$!: Observable; @Output() acceptedFilters = new EventEmitter>(); @@ -85,7 +85,7 @@ export class IssueFiltersComponent { this.filtersForm = this.fb.group({ status: new FormControl('', { nonNullable: true }), type: new FormControl('', { nonNullable: true }), - users: new FormControl('', { nonNullable: true }), + users: new FormControl([], { nonNullable: true }), issueRange: new FormGroup({ start: new FormControl(null), end: new FormControl(null), @@ -98,7 +98,7 @@ export class IssueFiltersComponent { } clearFilters(): void { - this.filtersForm.reset() + this.filtersForm.reset(); this.acceptedFilters.emit(this.filtersForm); } } diff --git a/src/ipa-bcfier-ui/src/app/components/project-details/project-details.component.ts b/src/ipa-bcfier-ui/src/app/components/project-details/project-details.component.ts index d862fe75..a3881d17 100644 --- a/src/ipa-bcfier-ui/src/app/components/project-details/project-details.component.ts +++ b/src/ipa-bcfier-ui/src/app/components/project-details/project-details.component.ts @@ -30,6 +30,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatIconModule } from '@angular/material/icon'; import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'; +import { UsersService } from '../../services/users.service'; @Component({ selector: 'bcfier-project-details', standalone: true, @@ -65,7 +66,8 @@ export class ProjectDetailsComponent { private projectUsersClient: ProjectUsersClient, private fb: FormBuilder, private cdr: ChangeDetectorRef, - private matDialog: MatDialog + private matDialog: MatDialog, + private usersService: UsersService ) { if (data) { this.projectDetailsForm.patchValue({ @@ -95,7 +97,8 @@ export class ProjectDetailsComponent { identifier: this.identifier, }) .pipe( - tap(() => { + tap((u) => { + this.usersService.setUsers(u); this.identifier = ''; this.cdr.detectChanges(); }) @@ -112,10 +115,9 @@ export class ProjectDetailsComponent { .afterClosed() .subscribe((confirm) => { if (confirm) { - this.users$ = this.projectUsersClient.deleteProjectUser( - this.data.id, - userId - ); + this.users$ = this.projectUsersClient + .deleteProjectUser(this.data.id, userId) + .pipe(tap((u) => this.usersService.setUsers(u))); this.cdr.detectChanges(); } }); diff --git a/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.html b/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.html index 25e4749a..0526de48 100644 --- a/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.html +++ b/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.html @@ -25,11 +25,22 @@ - delete +
+ + @if(row.id === selectedProject?.id) { bookmark } @else { + bookmark_border } + + + + delete + +
diff --git a/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.scss b/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.scss index bf871fd7..dc07eac3 100644 --- a/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.scss +++ b/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.scss @@ -26,3 +26,9 @@ mat-icon { tr.project-row:hover { background: whitesmoke; } + +.action-btn-container { + display: flex; + gap: 5px; + align-items: center; +} diff --git a/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.ts b/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.ts index f6aea01d..9230d4ee 100644 --- a/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.ts +++ b/src/ipa-bcfier-ui/src/app/components/projects-table/projects-table.component.ts @@ -3,6 +3,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + OnDestroy, + OnInit, ViewChild, inject, } from '@angular/core'; @@ -17,7 +19,14 @@ import { ProjectUsersClient, ProjectsClient, } from '../../generated-client/generated-client'; -import { combineLatestWith, filter, switchMap } from 'rxjs'; +import { + Subject, + combineLatestWith, + filter, + switchMap, + takeUntil, + tap, +} from 'rxjs'; import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'; import { FormsModule } from '@angular/forms'; @@ -30,6 +39,7 @@ import { NotificationsService } from '../../services/notifications.service'; import { ProjectDetailsComponent } from '../project-details/project-details.component'; import { ProjectsService } from '../../services/light-query/projects.service'; import { SettingsMessengerService } from '../../services/settings-messenger.service'; +import { SelectedProjectMessengerService } from '../../services/selected-project-messenger.service'; @Component({ selector: 'bcfier-projects-table', @@ -51,26 +61,49 @@ import { SettingsMessengerService } from '../../services/settings-messenger.serv styleUrl: './projects-table.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProjectsTableComponent implements AfterViewInit { +export class ProjectsTableComponent + implements AfterViewInit, OnDestroy, OnInit +{ @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; + projectsService = inject(ProjectsService); settingsMessengerService = inject(SettingsMessengerService); notificationsService = inject(NotificationsService); + selectedProjectMessengerService = inject(SelectedProjectMessengerService); projectsClient = inject(ProjectsClient); projectUsersClient = inject(ProjectUsersClient); matDialog = inject(MatDialog); cdr = inject(ChangeDetectorRef); + + private destroyed$ = new Subject(); dataSource!: MatTableDataSource; displayedColumns = ['name', 'created', 'actions']; filter = ''; + selectedProject: ProjectGet | null = null; constructor() { - this.projectsService.connect().subscribe((projects) => { - this.dataSource = new MatTableDataSource(projects); - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - }); + this.projectsService + .connect() + .pipe(takeUntil(this.destroyed$)) + .subscribe((projects) => { + this.dataSource = new MatTableDataSource(projects); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + }); + } + + ngOnInit(): void { + this.selectedProjectMessengerService.selectedProject + .pipe(takeUntil(this.destroyed$)) + .subscribe((p) => { + this.selectedProject = p; + }); + } + + ngOnDestroy(): void { + this.destroyed$.next(); + this.destroyed$.complete(); } ngAfterViewInit() { @@ -109,7 +142,8 @@ export class ProjectsTableComponent implements AfterViewInit { }) ) .subscribe({ - next: () => { + next: (p) => { + this.selectedProjectMessengerService.setSelectedProject(p); this.projectsService.forceRefresh(); if (this.filter) { this.applyFilter(this.filter); @@ -136,11 +170,18 @@ export class ProjectsTableComponent implements AfterViewInit { .createProject(newProject) .pipe(combineLatestWith(this.settingsMessengerService.settings)); }), - switchMap(([p, s]) => - this.projectUsersClient.addUserToProject(p.id, { - identifier: s.username, - }) - ) + + switchMap(([p, s]) => { + return this.projectUsersClient + .addUserToProject(p.id, { + identifier: s.username, + }) + .pipe( + tap(() => { + this.selectedProjectMessengerService.setSelectedProject(p); + }) + ); + }) ) .subscribe({ next: () => { @@ -177,4 +218,12 @@ export class ProjectsTableComponent implements AfterViewInit { error: () => {}, }); } + + setSelectedProject(p: ProjectGet): void { + if (p.id === this.selectedProject?.id) { + this.selectedProjectMessengerService.setSelectedProject(null); + return; + } + this.selectedProjectMessengerService.setSelectedProject(p); + } } diff --git a/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.html b/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.html index a5bf0a09..c7c3c4fc 100644 --- a/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.html +++ b/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.html @@ -21,6 +21,9 @@ Help +
+ {{ (selectedProject$ | async)?.name | uppercase }} +
IPA.BCFier v{{ version }} diff --git a/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.scss b/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.scss index b24554ea..242fc69a 100644 --- a/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.scss +++ b/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.scss @@ -12,3 +12,7 @@ .version-label { font-size: small; } + +.project-name { + font-size: 16px; +} diff --git a/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.ts b/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.ts index e0e7ad3e..b85dc41e 100644 --- a/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.ts +++ b/src/ipa-bcfier-ui/src/app/components/top-menu/top-menu.component.ts @@ -3,23 +3,31 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { BackendService } from '../../services/BackendService'; import { BcfFileWrapper } from '../../generated-client/generated-client'; import { BcfFilesMessengerService } from '../../services/bcf-files-messenger.service'; -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { NotificationsService } from '../../services/notifications.service'; import { SettingsComponent } from '../settings/settings.component'; import { version } from '../../version'; +import { AsyncPipe, UpperCasePipe } from '@angular/common'; +import { SelectedProjectMessengerService } from '../../services/selected-project-messenger.service'; @Component({ selector: 'bcfier-top-menu', standalone: true, - imports: [MatIconModule, MatButtonModule, MatDialogModule], + imports: [ + MatIconModule, + MatButtonModule, + MatDialogModule, + UpperCasePipe, + AsyncPipe, + ], templateUrl: './top-menu.component.html', styleUrl: './top-menu.component.scss', }) export class TopMenuComponent { version = version.version; - + selectedProject$ = inject(SelectedProjectMessengerService).selectedProject; constructor( private backendService: BackendService, private notificationsService: NotificationsService, diff --git a/src/ipa-bcfier-ui/src/app/components/topic-detail/topic-detail.component.html b/src/ipa-bcfier-ui/src/app/components/topic-detail/topic-detail.component.html index dd145cb1..e5e5f074 100644 --- a/src/ipa-bcfier-ui/src/app/components/topic-detail/topic-detail.component.html +++ b/src/ipa-bcfier-ui/src/app/components/topic-detail/topic-detail.component.html @@ -30,12 +30,14 @@ - Responsible - - @for (user of (users$ | async)?.values(); track user) { - {{ user }} + + @for (user of (users$ | async)?.values(); track user.id) { + {{ user.identifier }} } diff --git a/src/ipa-bcfier-ui/src/app/services/issue-filter.service.ts b/src/ipa-bcfier-ui/src/app/services/issue-filter.service.ts index b6715ec7..7b419165 100644 --- a/src/ipa-bcfier-ui/src/app/services/issue-filter.service.ts +++ b/src/ipa-bcfier-ui/src/app/services/issue-filter.service.ts @@ -10,7 +10,7 @@ export class IssueFilterService { issues: BcfTopic[], status: string, type: string, - users: any, + users: string[], dateStart: Date | null, dateEnd: Date | null ): BcfTopic[] { @@ -31,10 +31,15 @@ export class IssueFilterService { passesType = false; } - //TODO add filter by user - // if (users && users.length > 0 && !users.includes(issue.user)) { - // passesUsers = false; - // } + if ( + (users && + users.length > 0 && + issue.assignedTo && + !users.includes(issue.assignedTo)) || + (users.length > 0 && !issue.assignedTo) + ) { + passesUsers = false; + } if ( !!issue.dueDate && diff --git a/src/ipa-bcfier-ui/src/app/services/selected-project-messenger.service.ts b/src/ipa-bcfier-ui/src/app/services/selected-project-messenger.service.ts new file mode 100644 index 00000000..48c572e8 --- /dev/null +++ b/src/ipa-bcfier-ui/src/app/services/selected-project-messenger.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { ReplaySubject } from 'rxjs'; +import { ProjectGet } from '../generated-client/generated-client'; + +@Injectable({ + providedIn: 'root', +}) +export class SelectedProjectMessengerService { + private selectedProjectSource = new ReplaySubject(1); + selectedProject = this.selectedProjectSource.asObservable(); + + constructor() {} + + setSelectedProject(project: ProjectGet | null): void { + this.selectedProjectSource.next(project); + } +} diff --git a/src/ipa-bcfier-ui/src/app/services/users.service.ts b/src/ipa-bcfier-ui/src/app/services/users.service.ts index 2e19a4b1..e70e23e7 100644 --- a/src/ipa-bcfier-ui/src/app/services/users.service.ts +++ b/src/ipa-bcfier-ui/src/app/services/users.service.ts @@ -1,24 +1,38 @@ import { Injectable } from '@angular/core'; -import { ReplaySubject } from 'rxjs'; +import { BehaviorSubject, ReplaySubject, filter, switchMap } from 'rxjs'; +import { + ProjectUserGet, + ProjectUsersClient, +} from '../generated-client/generated-client'; +import { SelectedProjectMessengerService } from './selected-project-messenger.service'; @Injectable({ providedIn: 'root', }) export class UsersService { - //TODO replace type any - private usersSource = new ReplaySubject(1); + private usersSource = new BehaviorSubject([]); users = this.usersSource.asObservable(); - constructor() { + constructor( + private selectedProjectMessengerService: SelectedProjectMessengerService, + private projectUsersClient: ProjectUsersClient + ) { this.getAllUsers(); } - //TODO replace type any - setUsers(users: any[]): void { + setUsers(users: ProjectUserGet[]): void { this.usersSource.next(users); } getAllUsers(): void { - //TODO update, after add backend for getting users - this.setUsers(['Borys', 'Georg']); + this.selectedProjectMessengerService.selectedProject + .pipe( + filter((p) => !!p), + switchMap((p) => { + return this.projectUsersClient.getProjectUsersForProject(p?.id || ''); + }) + ) + .subscribe((users) => { + this.setUsers(users); + }); } }