diff --git a/src/app/shared/components/model-details-description/model-details-description.component.ts b/src/app/shared/components/model-details-description/model-details-description.component.ts index fe5026096f6484f2b5745f19bd517f3806a339f0..5c7107477544a56a624c58242d079962e655a745 100644 --- a/src/app/shared/components/model-details-description/model-details-description.component.ts +++ b/src/app/shared/components/model-details-description/model-details-description.component.ts @@ -2,7 +2,6 @@ import { Component, ElementRef, ViewChild } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute } from '@angular/router'; import { PublicSolutionsService } from 'src/app/core/services/public-solutions.service'; -import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; import { MatDividerModule } from '@angular/material/divider'; import { MatTabsModule } from '@angular/material/tabs'; import { MatProgressBarModule } from '@angular/material/progress-bar'; @@ -47,7 +46,6 @@ export class ModelDetailsDescriptionComponent { constructor( private activatedRoute: ActivatedRoute, private publicSolutionsService: PublicSolutionsService, - private browserStorageService: BrowserStorageService, private sharedDataService: SharedDataService, ) {} diff --git a/src/app/shared/components/share-with-team-page/share-with-team-page.component.html b/src/app/shared/components/share-with-team-page/share-with-team-page.component.html index 1ba35b7a3804fb890c494393c4523a7bd0bc7e8f..0c607ef1fab6d3ac77fcb8773aeb89198ebefa28 100644 --- a/src/app/shared/components/share-with-team-page/share-with-team-page.component.html +++ b/src/app/shared/components/share-with-team-page/share-with-team-page.component.html @@ -1 +1,148 @@ <div class="workflow-right-header workflow-header">Share With Team</div> +<div style="display: flex; flex-direction: column; gap: 20px; margin: 24px"> + <div> + <p class="manage-models"> + Sharing a model gives co-authorship to that person. They will be able to + edit, share and publish, just as a model owner can. + </p> + </div> + <mat-divider style="color: #dbcbe8"></mat-divider> + <div style="display: flex; flex-direction: column"> + <div style="display: flex; flex-direction: row" class="manage-models"> + <span>This Model shared with below team members</span>|<span + class="green" + > + ({{ sharedWith.length }} members)</span + > + </div> + <div> + <mat-chip-set style="justify-content: space-evenly !important"> + @for (sharedUser of sharedWith; track sharedUser) { + <mat-chip (removed)="removeUser(sharedUser)" style="width: 80px"> + <div style="display: flex; flex-direction: column; width: 100%"> + <div + style="display: flex; justify-content: flex-end; width: 100%" + > + <button + matChipRemove + [attr.aria-label]="'remove ' + sharedUser" + > + <mat-icon>cancel</mat-icon> + </button> + </div> + + <div + style=" + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + " + > + <img + style="width: 30px; height: 30px" + src="../../../../assets/images/user-profile-black.png" + /> + <span + class="team-user-box" + [title]=" + [sharedUser.firstName, sharedUser.lastName].join(' ') + " + > + {{ + [sharedUser.firstName, sharedUser.lastName].join(" ") + | truncate: 8 + }} + </span> + </div> + </div> + + <!-- <div + style="display: flex; flex-direction: row; justify-content: end" + > + <button matChipRemove [attr.aria-label]="'remove ' + sharedUser"> + <mat-icon>cancel</mat-icon> + </button> + </div> + <div + style=" + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + " + > + <img + style="width: 30px; height: 30px" + src="../../../../assets/images/user-profile-black.png" + /> + <span + [title]="[sharedUser.firstName, sharedUser.lastName].join(' ')" + > + {{ + [sharedUser.firstName, sharedUser.lastName].join(" ") + | truncate: 8 + }} + </span> + </div> --> + </mat-chip> + } + </mat-chip-set> + </div> + </div> + <mat-divider></mat-divider> + + <div + style="display: flex; flex-direction: row; gap: 4px" + class="manage-models" + > + <span class="purple">Sharing "{{ solution.name }}" </span> + |<span> Version - {{ selectedRevision.version }}</span> + | + <gp-solution-id [solutionId]="solutionId"></gp-solution-id> + </div> + <form> + <mat-form-field class="example-chip-list"> + <mat-label>Find a user to share with</mat-label> + <mat-chip-grid #chipGrid> + @for (user of users; track user) { + <mat-chip-row (removed)="remove(user)"> + {{ user.firstName }} {{ user.lastName }} + <button matChipRemove [attr.aria-label]="'remove ' + user"> + <mat-icon>cancel</mat-icon> + </button> + </mat-chip-row> + } + </mat-chip-grid> + <input + #userInput + [formControl]="userCtrl" + [matChipInputFor]="chipGrid" + [matAutocomplete]="auto" + [matChipInputSeparatorKeyCodes]="separatorKeysCodes" + /> + <mat-autocomplete + #auto="matAutocomplete" + (optionSelected)="selected($event)" + > + @for (user of filteredUsers | async; track user) { + <mat-option [value]="user" + ><span style="overflow: hidden; white-space: nowrap" + >{{ user.firstName }} {{ user.lastName }}</span + ></mat-option + > + } + </mat-autocomplete> + </mat-form-field> + </form> + + <button + style="width: 80px" + [disabled]="users.length <= 0" + color="primary" + mat-raised-button + (click)="OnclickShareButton()" + > + Share + </button> +</div> diff --git a/src/app/shared/components/share-with-team-page/share-with-team-page.component.scss b/src/app/shared/components/share-with-team-page/share-with-team-page.component.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..78a13a7982d6850cb67922f95cdb85fff928f75b 100644 --- a/src/app/shared/components/share-with-team-page/share-with-team-page.component.scss +++ b/src/app/shared/components/share-with-team-page/share-with-team-page.component.scss @@ -0,0 +1,74 @@ +.example-chip-list { + width: 100%; +} + +.mat-divider { + border-top-color: #dbcbe8 !important; + border-top-width: 1px !important; +} + +.mat-mdc-chip.mat-mdc-standard-chip { + --mdc-chip-container-height: 100% !important; +} + +::ng-deep.mat-mdc-standard-chip.mdc-evolution-chip--with-trailing-action + .mdc-evolution-chip__action--primary { + padding: 0px !important; + width: 100% !important; +} + +::ng-deep.mat-mdc-standard-chip { + --mdc-chip-container-shape-radius: 3px 3x 3px 3px; + border: 1px solid #e0e0e0 !important; + --mdc-chip-elevated-container-color: transparent !important; +} + +::ng-deep.mat-mdc-standard-chip.mdc-evolution-chip--with-trailing-action + .mdc-evolution-chip__action--trailing { + padding-left: 0px !important; + padding-right: 2px !important; +} +::ng-deep.mat-mdc-standard-chip .mdc-evolution-chip__cell--primary, +::ng-deep.mat-mdc-standard-chip .mdc-evolution-chip__action--primary, +::ng-deep.mat-mdc-standard-chip .mat-mdc-chip-action-label { + width: 100% !important; +} +.team-user-box { + padding: 0 5px; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 11px; + color: 666; + font-weight: 600; +} + +.manage-models { + font-size: 14px; + letter-spacing: 0; + line-height: 24px; + word-break: break-word; + overflow-wrap: break-word; + hyphens: auto; +} + +.purple { + color: #671c9d; +} + +.green { + color: #009901; +} + +.content { + flex: 1; + display: flex; + flex-direction: column; + gap: 20px; +} + +.actions { + display: flex; + justify-content: flex-end; +} diff --git a/src/app/shared/components/share-with-team-page/share-with-team-page.component.ts b/src/app/shared/components/share-with-team-page/share-with-team-page.component.ts index 8a871d03b142a9070590ff5f8947aeea22f271c6..ebb19a4ecdbc1dce3cfc65c04f6bab2a2b62ea47 100644 --- a/src/app/shared/components/share-with-team-page/share-with-team-page.component.ts +++ b/src/app/shared/components/share-with-team-page/share-with-team-page.component.ts @@ -1,13 +1,270 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { + Component, + ElementRef, + OnInit, + ViewChild, + inject, +} from '@angular/core'; +import { AsyncPipe, CommonModule } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatDividerModule } from '@angular/material/divider'; +import { + MatAutocompleteModule, + MatAutocompleteSelectedEvent, +} from '@angular/material/autocomplete'; +import { Observable, Subscription, map, startWith } from 'rxjs'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatChipInputEvent, MatChipsModule } from '@angular/material/chips'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import { COMMA, ENTER } from '@angular/cdk/keycodes'; +import { PrivateCatalogsService } from 'src/app/core/services/private-catalogs.service'; +import { + Alert, + AlertType, + PublicSolutionDetailsModel, + Revision, + UserDetails, +} from '../../models'; +import { ActivatedRoute } from '@angular/router'; +import { PublicSolutionsService } from 'src/app/core/services/public-solutions.service'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { DeleteUserDialogConfirmationActionComponent } from '../delete-user-dialog-confirmation-action/delete-user-dialog-confirmation-action.component'; +import { AlertService } from 'src/app/core/services/alert.service'; +import { SharedDataService } from 'src/app/core/services/shared-data/shared-data.service'; +import { SolutionIdComponent } from '../solution-id/solution-id.component'; +import { TruncatePipe } from '../../pipes/truncate.pipe'; + +export interface State { + flag: string; + name: string; + population: string; +} @Component({ selector: 'gp-share-with-team-page', standalone: true, - imports: [CommonModule], + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + FormsModule, + MatFormFieldModule, + MatChipsModule, + MatIconModule, + MatAutocompleteModule, + ReactiveFormsModule, + MatDividerModule, + AsyncPipe, + SolutionIdComponent, + TruncatePipe, + ], templateUrl: './share-with-team-page.component.html', - styleUrl: './share-with-team-page.component.scss' + styleUrl: './share-with-team-page.component.scss', }) -export class ShareWithTeamPageComponent { +export class ShareWithTeamPageComponent implements OnInit { + separatorKeysCodes: number[] = [ENTER, COMMA]; + fruitCtrl = new FormControl(''); + userCtrl = new FormControl(''); + filteredFruits: Observable<string[]>; + filteredUsers: Observable<UserDetails[]>; + + fruits: string[] = ['']; + users: UserDetails[] = []; + allFruits: string[] = ['Apple', 'Lemon', 'Lime', 'Orange', 'Strawberry']; + allUsersList: UserDetails[] = []; + + allUserDetails: UserDetails[] = []; + sharedWith: UserDetails[] = []; + + solutionId!: string; + revisionId!: string; + selectedRevision!: Revision; + selectedRevisionSubscription!: Subscription; + solution!: PublicSolutionDetailsModel; + + @ViewChild('fruitInput') fruitInput!: ElementRef<HTMLInputElement>; + @ViewChild('userInput') userInput!: ElementRef<HTMLInputElement>; + + announcer = inject(LiveAnnouncer); + + constructor( + private publicSolutionsService: PublicSolutionsService, + private privateCatalogsService: PrivateCatalogsService, + private sharedDataService: SharedDataService, + private activatedRoute: ActivatedRoute, + public dialog: MatDialog, + private alertService: AlertService, + ) { + this.filteredFruits = this.fruitCtrl.valueChanges.pipe( + startWith(null), + map((fruit: string | null) => + fruit ? this._filter(fruit) : this.allFruits.slice(), + ), + ); + + this.filteredUsers = this.userCtrl.valueChanges.pipe( + startWith(''), // Start with an empty string + map((value) => { + return this.transformValue(value); + }), + map((name) => + name ? this._filterUser(name) : this.allUsersList.slice(), + ), + ); + } + + transformValue(value: string | UserDetails | null): string { + if (typeof value === 'string') { + return value; // Directly return the string + } else if (value) { + return this.displayFn(value); // Convert UserDetails to string if not null + } + return ''; // Return empty string if value is null + } + + displayFn(user: UserDetails): string { + return user ? `${user.firstName} ${user.lastName}`.trim() : ''; + } + + ngOnInit(): void { + this.activatedRoute.parent?.params.subscribe((params) => { + this.solutionId = params['solutionId']; + this.revisionId = params['revisionId']; + this.getShareWithTeam(this.solutionId); + this.loadSolutionDetails(this.solutionId, this.revisionId); + this.selectedRevisionSubscription = + this.sharedDataService.selectedRevision$.subscribe( + (revision: Revision) => { + this.selectedRevision = revision; + }, + ); + }); + } + + /* addUser(event: MatChipInputEvent): void { + const value = (event.value || '').trim(); + this.allUsersList.filter( + (user) => + user.firstName.toLowerCase().includes(value) || + user.lastName?.toLowerCase().includes(value), + ); + } */ + + remove(user: UserDetails) { + const index = this.users.indexOf(user); + + if (index >= 0) { + this.users.splice(index, 1); + + this.announcer.announce(`Removed ${user}`); + } + } + + selected(event: MatAutocompleteSelectedEvent): void { + this.users.push(event.option.value); + this.userInput.nativeElement.value = ''; + this.userCtrl.setValue(null); + } + + private _filter(value: string): string[] { + const filterValue = value.toLowerCase(); + + return this.allFruits.filter((fruit) => + fruit.toLowerCase().includes(filterValue), + ); + } + + private _filterUser(value: string): UserDetails[] { + const filterValue = value.toLowerCase(); + + return this.allUsersList.filter( + (user) => + user.firstName.toLowerCase().includes(filterValue) || + user.lastName?.toLowerCase().includes(filterValue), + ); + } + + getUsersChips(): void { + const userActiveStatus = true; + this.privateCatalogsService.getAllActiveUsers(userActiveStatus).subscribe({ + next: (users) => { + const sharedWithIds = new Set( + this.sharedWith.map((user) => user.userId), + ); + this.allUserDetails = users.filter((user) => { + return ( + !sharedWithIds.has(user.userId) && + user.userId != this.solution.ownerId + ); + }); + + this.allUsersList = this.allUserDetails; + }, + error: (error) => { + console.error('Error fetching users:', error); + }, + }); + } + + getShareWithTeam(solutionId: string): void { + this.privateCatalogsService.getShareWithTeam(solutionId).subscribe({ + next: (users) => { + this.sharedWith = users; + this.getUsersChips(); + }, + error: (error) => { + console.error('Error fetching users:', error); + }, + }); + } + + loadSolutionDetails(solutionId: string, revisionId: string) { + this.publicSolutionsService + .getSolutionDetails(solutionId, revisionId) + .subscribe((res) => { + this.solution = res; + }); + } + + OnclickShareButton() { + const selectedUsersIds: string[] = this.users.map( + (user) => user.userId ?? '', + ); + + this.privateCatalogsService + .insertMultipleShare(selectedUsersIds, this.solutionId) + .subscribe((res) => { + const alert: Alert = { + message: '', + type: AlertType.Success, + }; + + if (res.error_code === '100') { + alert.message = 'Shared successfully. '; + } + + this.alertService.notify(alert, 3000); + this.getShareWithTeam(this.solutionId); + this.users = []; + }); + } + + removeUser(user: UserDetails) { + const dialogRef: MatDialogRef<DeleteUserDialogConfirmationActionComponent> = + this.dialog.open(DeleteUserDialogConfirmationActionComponent, { + data: { + dataKey: { + solutionId: this.solutionId, + user: user, + }, + }, + autoFocus: false, + }); + dialogRef.afterClosed().subscribe((result) => { + this.getShareWithTeam(this.solutionId); + }); + } }