From 73a20432ead3e123eccee4304f3be5b2661397cd Mon Sep 17 00:00:00 2001 From: Alexia <74705079+TheDarkestQueen@users.noreply.github.com> Date: Wed, 11 Jan 2023 01:23:41 +0100 Subject: [PATCH 01/12] change password, delete account, UX improvement --- .../change-password/change-password.page.ts | 2 +- .../delete-account/delete-account.page.ts | 2 +- src/app/auth/pages/register/register.page.ts | 2 +- src/app/auth/services/auth/auth.service.ts | 44 ++++++++- .../change-password.dialog.html | 26 ++++++ .../change-password.dialog.scss | 9 ++ .../change-password/change-password.dialog.ts | 93 +++++++++++++++++++ .../settings-account.page.html | 2 +- .../settings-account/settings-account.page.ts | 16 ++-- src/app/settings/settings.module.ts | 2 + 10 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 src/app/settings/dialog/change-password/change-password.dialog.html create mode 100644 src/app/settings/dialog/change-password/change-password.dialog.scss create mode 100644 src/app/settings/dialog/change-password/change-password.dialog.ts diff --git a/src/app/auth/pages/change-password/change-password.page.ts b/src/app/auth/pages/change-password/change-password.page.ts index 78e2cdb67..ff7683754 100644 --- a/src/app/auth/pages/change-password/change-password.page.ts +++ b/src/app/auth/pages/change-password/change-password.page.ts @@ -65,7 +65,7 @@ export class ChangePasswordPage implements OnInit { if (this.form.valid && this.token) { this.resetSubscription = this.authService - .setNewPassword(this.token, this.form.value.password) + .setNewPasswordFromToken(this.token, this.form.value.password) .subscribe(() => { this.router.navigate(['/auth/login']); }); diff --git a/src/app/auth/pages/delete-account/delete-account.page.ts b/src/app/auth/pages/delete-account/delete-account.page.ts index 8dd6e083c..a206814ff 100644 --- a/src/app/auth/pages/delete-account/delete-account.page.ts +++ b/src/app/auth/pages/delete-account/delete-account.page.ts @@ -28,7 +28,7 @@ export class DeleteAccountPage implements OnInit { */ deleteAccount(token: string) { if (token) { - this.authService.deleteAccountConfirmation(token).subscribe(() => {}); + this.authService.deleteAccount(token).subscribe(() => {}); } } } diff --git a/src/app/auth/pages/register/register.page.ts b/src/app/auth/pages/register/register.page.ts index 7275dc386..4c88efacc 100644 --- a/src/app/auth/pages/register/register.page.ts +++ b/src/app/auth/pages/register/register.page.ts @@ -58,7 +58,7 @@ export class RegisterPage { repeatPassword: new FormControl('', [ requiredValidator(), passwordValidator(), - sameAsValidator('password', $localize`Given passwords are not the same `), + sameAsValidator('password', $localize`Given passwords are not the same`), ]), name: new FormControl('', [requiredValidator()]), surname: new FormControl('', [requiredValidator()]), diff --git a/src/app/auth/services/auth/auth.service.ts b/src/app/auth/services/auth/auth.service.ts index 720853ae8..6a49e87c7 100644 --- a/src/app/auth/services/auth/auth.service.ts +++ b/src/app/auth/services/auth/auth.service.ts @@ -1,13 +1,16 @@ import { Injectable, Injector } from '@angular/core'; import { Service } from '@main/decorators/service/service.decorator'; import dayjs from 'dayjs'; -import { tap, Observable, switchMap, catchError, map, of } from 'rxjs'; +import { tap, Observable, switchMap, catchError, map, of, EMPTY } from 'rxjs'; import { ApiService } from '@main/services/api/api.service'; import { BaseService } from '@main/services/base/base.service'; import { Errors } from '@main/interfaces/http-error.interface'; import { User } from '../../interfaces/user.interface'; import { ReCaptchaV3Service } from 'ng-recaptcha'; import { ProtoService } from '@main/services/proto/proto.service'; +import { AlertDialogVariant } from '@main/dialogs/alert/alert.dialog'; +import { DialogService } from '@main/services/dialog/dialog.service'; +import { ChangePasswordDialog } from 'src/app/settings/dialog/change-password/change-password.dialog'; /** * Authentication service @@ -28,6 +31,7 @@ export class AuthService extends BaseService> { private apiService: ApiService, private recaptchaV3Service: ReCaptchaV3Service, private protoService: ProtoService, + private dialogService: DialogService, ) { super(injector); } @@ -116,24 +120,56 @@ export class AuthService extends BaseService> { * @param password new password * @returns set new password response */ - public setNewPassword(token: string, password: string) { + public setNewPasswordFromToken(token: string, password: string) { return this.apiService.post(`/auth/password/reset`, { body: { token, password } }); } + public changePassword(oldPassword: string, newPassword: string) { + return this.apiService.post(`/auth/password/change`, { body: { oldPassword, newPassword } }); + } + + public openChangePasswordDialog() { + return this.dialogService.open(ChangePasswordDialog, {}).afterClosed(); + } + /** * Delete account * @returns delete account response */ - public deleteAccount() { + public sendEmailToDeleteAccount() { return this.apiService.delete(`/auth/delete`); } + /** + * Delete account with confirmation + */ + public deleteAccountWithConfirmation(): Observable { + return this.dialogService + .confirm({ + title: $localize`Delete account`, + message: $localize`Upon proceeding, an email with a deletion link will be send to your email account. To delete account you will need to click given link. Are you sure you want to proceed with deleting your account? Please note that this action will log you out.`, + confirmText: $localize`Delete`, + cancelText: $localize`Cancel`, + variant: AlertDialogVariant.IMPORTANT, + }) + .pipe( + switchMap((confirmed) => { + if (!confirmed) return EMPTY; + return this.sendEmailToDeleteAccount().pipe( + switchMap(() => { + return this.logout(); + }), + ); + }), + ); + } + /** * Delete account confirmation * @param token delete account token received by email * @returns delete account confirmation response */ - public deleteAccountConfirmation(token: string) { + public deleteAccount(token: string) { return this.apiService.delete(`/auth/delete/confirm`, { body: { token } }).pipe( this.validate({ 403: 'INVALID_TOKEN', diff --git a/src/app/settings/dialog/change-password/change-password.dialog.html b/src/app/settings/dialog/change-password/change-password.dialog.html new file mode 100644 index 000000000..0d44e0f3b --- /dev/null +++ b/src/app/settings/dialog/change-password/change-password.dialog.html @@ -0,0 +1,26 @@ +

Change password

+
+
+ +
+ {{ error }} +
+ + + + + + + + + + + + + +
+
+
+ Cancel + Save +
diff --git a/src/app/settings/dialog/change-password/change-password.dialog.scss b/src/app/settings/dialog/change-password/change-password.dialog.scss new file mode 100644 index 000000000..d26a3e884 --- /dev/null +++ b/src/app/settings/dialog/change-password/change-password.dialog.scss @@ -0,0 +1,9 @@ +form { + display: flex; + flex-direction: column; + padding-top: 1rem; + + &>*:not(:last-child):not(app-checkbox) { + margin-bottom: 1.5rem; + } +} diff --git a/src/app/settings/dialog/change-password/change-password.dialog.ts b/src/app/settings/dialog/change-password/change-password.dialog.ts new file mode 100644 index 000000000..ef3b1ccc0 --- /dev/null +++ b/src/app/settings/dialog/change-password/change-password.dialog.ts @@ -0,0 +1,93 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { requiredValidator } from '@main/validators/required.validator'; +import { FormControl, FormGroup } from '@ngneat/reactive-forms'; +import { validateForm } from '@main/classes/form.class'; +import { AuthService } from '@auth/services/auth/auth.service'; +import { SnackbarService } from '@main/services/snackbar/snackbar.service'; +import { catchError, throwError } from 'rxjs'; +import { withLoader } from '@main/operators/loader.operator'; +import { Loader } from '@main/classes/loader/loader.class'; +import { passwordValidator } from '@main/validators/password.validator'; +import { sameAsValidator } from '@main/validators/same-as.validator'; + +/** + * Change password dialog component + */ +@Component({ + selector: 'change-password-dialog', + templateUrl: './change-password.dialog.html', + styleUrls: ['./change-password.dialog.scss'], +}) +export class ChangePasswordDialog implements OnInit { + /** + * Login error + */ + public error?: string; + + /** + * Loader + */ + public loader = new Loader(); + + /** Change password dialog form */ + public form = new FormGroup({ + oldPassword: new FormControl('', [requiredValidator(), passwordValidator()]), + newPassword: new FormControl('', [requiredValidator(), passwordValidator()]), + rep_newPassword: new FormControl('', [ + requiredValidator(), + passwordValidator(), + sameAsValidator('newPassword', $localize`Given passwords are not the same`), + ]), + }); + + constructor( + private dialogRef: MatDialogRef, + private authService: AuthService, + private snackbarService: SnackbarService, + ) {} + + ngOnInit() {} + + /** + * Cancel dialog + */ + cancel() { + this.dialogRef.close(); + } + + /** + * Closes dialog + */ + save() { + if (validateForm(this.form)) { + this.error = undefined; + let data = this.form.value; + this.authService + .changePassword(data.oldPassword, data.newPassword) + .pipe( + withLoader(this.loader), + catchError((error) => { + this.handleError(error); + return throwError(() => new Error(error)); + }), + ) + .subscribe(() => { + this.dialogRef.close(); + this.snackbarService.show($localize`Password changed successfully!`); + }); + } + } + + /** + * Handle chaneg password error + * @param error Error + */ + handleError(error: any) { + switch (error.status) { + case 404: + this.error = $localize`Old password is incorrect.`; + break; + } + } +} diff --git a/src/app/settings/pages/settings-account/settings-account.page.html b/src/app/settings/pages/settings-account/settings-account.page.html index 3818fa12b..341accdfd 100644 --- a/src/app/settings/pages/settings-account/settings-account.page.html +++ b/src/app/settings/pages/settings-account/settings-account.page.html @@ -32,7 +32,7 @@

Account

It's highly recommended to change the password every 30 days. It will increase your account security.
- Change password + Change password
diff --git a/src/app/settings/pages/settings-account/settings-account.page.ts b/src/app/settings/pages/settings-account/settings-account.page.ts index 17dd3a956..378af4d4d 100644 --- a/src/app/settings/pages/settings-account/settings-account.page.ts +++ b/src/app/settings/pages/settings-account/settings-account.page.ts @@ -4,6 +4,7 @@ import { AuthService } from '@auth/services/auth/auth.service'; import { UserService } from '@auth/services/user/user.service'; import { requiredValidator } from '@main/validators/required.validator'; import { SnackbarService } from '@main/services/snackbar/snackbar.service'; +import { Router } from '@angular/router'; /** * Settings account page component @@ -18,6 +19,7 @@ export class SettingsAccountPage implements OnInit { private userService: UserService, private authService: AuthService, private snackbarService: SnackbarService, + private router: Router, ) {} /** Form to edit account data */ @@ -48,15 +50,11 @@ export class SettingsAccountPage implements OnInit { } /** - * Reset password + * Change password * @TODO Add confirmation dialog */ - resetPassword() { - const email = this.form.get('email').value; - - this.authService.resetPassword({ email }).subscribe(() => { - this.authService.logout().subscribe(); - }); + changePassword() { + this.authService.openChangePasswordDialog().subscribe(); } /** @@ -64,8 +62,8 @@ export class SettingsAccountPage implements OnInit { * @TODO Add confirmation dialog */ deleteAccountMailCheck() { - this.authService.deleteAccount().subscribe(() => { - this.authService.logout().subscribe(); + this.authService.deleteAccountWithConfirmation().subscribe(() => { + this.router.navigate(['/']); }); } } diff --git a/src/app/settings/settings.module.ts b/src/app/settings/settings.module.ts index a493bfeea..cae922db2 100644 --- a/src/app/settings/settings.module.ts +++ b/src/app/settings/settings.module.ts @@ -5,6 +5,7 @@ import { MainModule } from '@main/_main.module'; import { SettingsPage } from 'src/app/settings/pages/settings/settings.page'; import { IntegrationEntryComponent } from './components/integration-entry/integration-entry.component'; import { ListGroupComponent } from './components/list-group/list-group.component'; +import { ChangePasswordDialog } from './dialog/change-password/change-password.dialog'; import { SettingsAccountPage } from './pages/settings-account/settings-account.page'; import { SettingsIntegrationsPage } from './pages/settings-integrations/settings-integrations.page'; import { SettingsLocalizationPage } from './pages/settings-localization/settings-localization.page'; @@ -21,6 +22,7 @@ import { SettingsRoutingModule } from './settings.routing'; SettingsSessionsPage, ListGroupComponent, IntegrationEntryComponent, + ChangePasswordDialog, ], }) export class SettingsModule {} From 7bdc8f00e1d2441a4e46e572b9c3fda29c4998dd Mon Sep 17 00:00:00 2001 From: Alexia <74705079+TheDarkestQueen@users.noreply.github.com> Date: Wed, 11 Jan 2023 01:52:06 +0100 Subject: [PATCH 02/12] fix unit tests --- src/app/auth/services/auth/auth.service.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/auth/services/auth/auth.service.spec.ts b/src/app/auth/services/auth/auth.service.spec.ts index 6d7caa761..ac5ff5a53 100644 --- a/src/app/auth/services/auth/auth.service.spec.ts +++ b/src/app/auth/services/auth/auth.service.spec.ts @@ -3,6 +3,7 @@ import { HttpClientModule } from '@angular/common/http'; import { TestBed, inject } from '@angular/core/testing'; import { AuthService } from './auth.service'; +import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { RECAPTCHA_V3_SITE_KEY, ReCaptchaV3Service } from 'ng-recaptcha'; import { environment } from 'src/environments/environment'; @@ -12,6 +13,9 @@ describe('Service: AuthService', () => { imports: [HttpClientModule], providers: [ AuthService, + MatDialogModule, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: MatDialogRef, useValue: {} }, { provide: RECAPTCHA_V3_SITE_KEY, useValue: environment.captchaSiteKey }, { provide: ReCaptchaV3Service, useClass: ReCaptchaV3Service }, ], From 037b33536b2e08d771fa61d5140b73c6aa666b39 Mon Sep 17 00:00:00 2001 From: Alexia <74705079+TheDarkestQueen@users.noreply.github.com> Date: Wed, 11 Jan 2023 01:59:30 +0100 Subject: [PATCH 03/12] fix --- src/app/auth/services/auth/auth.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/auth/services/auth/auth.service.spec.ts b/src/app/auth/services/auth/auth.service.spec.ts index ac5ff5a53..f36bff3ae 100644 --- a/src/app/auth/services/auth/auth.service.spec.ts +++ b/src/app/auth/services/auth/auth.service.spec.ts @@ -10,7 +10,7 @@ import { environment } from 'src/environments/environment'; describe('Service: AuthService', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientModule], + imports: [HttpClientModule, MatDialogModule], providers: [ AuthService, MatDialogModule, From d8f1de75aae2e31890c4c7110a4af3b87321a8ad Mon Sep 17 00:00:00 2001 From: Alexia <74705079+TheDarkestQueen@users.noreply.github.com> Date: Wed, 11 Jan 2023 02:07:38 +0100 Subject: [PATCH 04/12] fix of the fix ;) --- src/app/auth/services/user/user.service.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/auth/services/user/user.service.spec.ts b/src/app/auth/services/user/user.service.spec.ts index bf3df9211..8c4008391 100644 --- a/src/app/auth/services/user/user.service.spec.ts +++ b/src/app/auth/services/user/user.service.spec.ts @@ -4,12 +4,13 @@ import { HttpClientModule } from '@angular/common/http'; import { inject, TestBed } from '@angular/core/testing'; import { UserService } from './user.service'; import { RECAPTCHA_V3_SITE_KEY, ReCaptchaV3Service } from 'ng-recaptcha'; +import { MatDialogModule } from '@angular/material/dialog'; import { environment } from '../../../../environments/environment'; describe('Service: User', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientModule], + imports: [HttpClientModule, MatDialogModule], providers: [ UserService, { provide: RECAPTCHA_V3_SITE_KEY, useValue: environment.captchaSiteKey }, From 71921cd2d089a92798425b22fedcd108a9d59cf3 Mon Sep 17 00:00:00 2001 From: Marcin Czerniak Date: Wed, 11 Jan 2023 10:22:39 +0100 Subject: [PATCH 05/12] Feature/dashboard (#56) * Dashboard core components * Dashboard widgets, no workspaces alert * Add no projects message * Add loaders to projects and workspaces --- .../sidebar-navigation.component.html | 136 +++++++++++------- .../audit-log/audit-log.component.html | 13 +- .../audit-log/audit-log.component.ts | 2 + .../audit-log-project-entries.pipe.ts | 20 ++- src/app/dashboard/dashboard.module.ts | 12 ++ src/app/dashboard/dashboard.routing.ts | 6 +- .../widget-audit-log.component.html | 15 ++ .../widget-audit-log.component.scss | 18 +++ .../widget-audit-log.component.ts | 65 +++++++++ .../widget-content.component.html | 1 + .../widget-content.component.scss | 5 + .../widget-content.component.ts | 8 ++ .../widget-header.component.html | 3 + .../widget-header.component.scss | 11 ++ .../widget-header/widget-header.component.ts | 10 ++ .../widget-tasks/widget-tasks.component.html | 17 +++ .../widget-tasks/widget-tasks.component.scss | 18 +++ .../widget-tasks/widget-tasks.component.ts | 68 +++++++++ .../create-project/create-project.page.ts | 4 +- .../pages/dashboard/dashboard.page.html | 2 + .../pages/dashboard/dashboard.page.scss | 0 .../pages/dashboard/dashboard.page.ts | 8 ++ .../projects-list/projects-list.page.html | 115 +++++++++------ .../projects-list/projects-list.page.scss | 29 ++++ .../pages/projects-list/projects-list.page.ts | 12 ++ .../workspaces-list/workspaces-list.page.html | 115 +++++++++------ .../workspaces-list/workspaces-list.page.scss | 29 ++++ .../workspaces-list/workspaces-list.page.ts | 23 +-- src/app/messages/messages.module.ts | 2 + src/app/messages/messages.routing.ts | 5 + .../messages-no-integration.page.html | 10 ++ .../messages-no-integration.page.scss | 26 ++++ .../messages-no-integration.page.ts | 8 ++ .../task-list/task-list.component.html | 14 +- .../task-list/task-list.component.ts | 15 +- .../task-row/task-row.component.html | 15 +- .../components/task-row/task-row.component.ts | 2 + src/app/tasks/services/task/task.service.ts | 12 ++ 38 files changed, 693 insertions(+), 181 deletions(-) create mode 100644 src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.html create mode 100644 src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.scss create mode 100644 src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.ts create mode 100644 src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.html create mode 100644 src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.scss create mode 100644 src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.ts create mode 100644 src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.html create mode 100644 src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.scss create mode 100644 src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.ts create mode 100644 src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.html create mode 100644 src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.scss create mode 100644 src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.ts create mode 100644 src/app/dashboard/pages/dashboard/dashboard.page.html create mode 100644 src/app/dashboard/pages/dashboard/dashboard.page.scss create mode 100644 src/app/dashboard/pages/dashboard/dashboard.page.ts create mode 100644 src/app/messages/pages/messages-no-integration/messages-no-integration.page.html create mode 100644 src/app/messages/pages/messages-no-integration/messages-no-integration.page.scss create mode 100644 src/app/messages/pages/messages-no-integration/messages-no-integration.page.ts diff --git a/src/app/_main/components/sidebar-navigation/sidebar-navigation.component.html b/src/app/_main/components/sidebar-navigation/sidebar-navigation.component.html index 40a94d8d2..615758a29 100644 --- a/src/app/_main/components/sidebar-navigation/sidebar-navigation.component.html +++ b/src/app/_main/components/sidebar-navigation/sidebar-navigation.component.html @@ -12,7 +12,12 @@ Calendar - + + Messages + + Messages
- + + Workspaces + + + + + + + Workspaces
- - {{ workspace.name }} -
- - {{ projectWithPrivileges.project.name }} - - - - - - - - -
- - - - - - -
+ + + {{ workspace.name }} + + + + + + + + + + {{ workspace.name }} +
+ + {{ projectWithPrivileges.project.name }} + + + + + + + + +
+ + + + + + +
+
diff --git a/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.html b/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.html index c9dee8f99..da1d209f8 100644 --- a/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.html +++ b/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.html @@ -1,4 +1,9 @@ - - - + + + + + + + + diff --git a/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.ts b/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.ts index c363395f6..9c780c4c2 100644 --- a/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.ts +++ b/src/app/_main/modules/audit-log/components/audit-log/audit-log.component.ts @@ -9,6 +9,8 @@ import { AuditLog } from '../../interfaces/audit-log.interface'; export class AuditLogComponent { @Input() auditLogs!: AuditLog[]; + @Input() noScroll: boolean = false; + public trackByAuditLog(index: number, auditLog: AuditLog) { return auditLog.date; } diff --git a/src/app/_main/modules/audit-log/pipes/audit-log-project-entries/audit-log-project-entries.pipe.ts b/src/app/_main/modules/audit-log/pipes/audit-log-project-entries/audit-log-project-entries.pipe.ts index 74c20a4d2..1b8eec3f6 100644 --- a/src/app/_main/modules/audit-log/pipes/audit-log-project-entries/audit-log-project-entries.pipe.ts +++ b/src/app/_main/modules/audit-log/pipes/audit-log-project-entries/audit-log-project-entries.pipe.ts @@ -148,12 +148,20 @@ export class AuditLogProjectEntriesPipe implements PipeTransform { case 'assigneeId': return { label: $localize`assignee`, - oldValue: members.find( - (member) => member.user.id === auditLog.oldValues?.assigneeId, - )?.user.name, - newValue: members.find( - (member) => member.user.id === auditLog.newValues?.assigneeId, - )?.user.name, + oldValue: (() => { + const member = members.find( + (member) => member.user.id === auditLog.oldValues?.assigneeId, + ); + if (!member) + return $localize`:unknown user|ctx. Changed assignee from unknown user to someone else:Unknown`; + return `${member?.user.name} ${member?.user.surname}`; + })(), + newValue: (() => { + const member = members.find( + (member) => member.user.id === auditLog.newValues?.assigneeId, + ); + return `${member?.user.name} ${member?.user.surname}`; + })(), }; case 'sprintId': return { diff --git a/src/app/dashboard/dashboard.module.ts b/src/app/dashboard/dashboard.module.ts index 01cab6928..93a1e21bd 100644 --- a/src/app/dashboard/dashboard.module.ts +++ b/src/app/dashboard/dashboard.module.ts @@ -14,6 +14,8 @@ import { DashboardRoutingModule } from './dashboard.routing'; import { AddMemberDialog } from './dialogs/add-member/add-member.dialog'; import { StatusDialog } from './dialogs/status/status.dialog'; import { IntegrationModulesModule } from './modules/integration-modules/integration-modules.module'; +import { WidgetContentComponent } from './modules/widgets/components/widget-content/widget-content.component'; +import { WidgetHeaderComponent } from './modules/widgets/components/widget-header/widget-header.component'; import { CreateProjectPage } from './pages/create-project/create-project.page'; import { CreateWorkspacePage } from './pages/create-workspace/create-workspace.page'; import { EditProjectPage } from './pages/edit-project/edit-project.page'; @@ -22,6 +24,10 @@ import { ProjectPage } from './pages/project/project.page'; import { ProjectsListPage } from './pages/projects-list/projects-list.page'; import { WorkspacesListPage } from './pages/workspaces-list/workspaces-list.page'; import { GitIntegrationService } from './services/git-integration/git-integration.service'; +import { WidgetTasksComponent } from './modules/widgets/components/widget-tasks/widget-tasks.component'; +import { DashboardPage } from './pages/dashboard/dashboard.page'; +import { WidgetAuditLogComponent } from './modules/widgets/components/widget-audit-log/widget-audit-log.component'; +import { AuditLogModule } from '../_main/modules/audit-log/audit-log.module'; @NgModule({ imports: [ @@ -31,6 +37,7 @@ import { GitIntegrationService } from './services/git-integration/git-integratio DashboardRoutingModule, IntegrationModulesModule, TasksModule, + AuditLogModule, ], declarations: [ CreateWorkspacePage, @@ -49,6 +56,11 @@ import { GitIntegrationService } from './services/git-integration/git-integratio AddMemberDialog, IntegrationModulesGridComponent, StatusDialog, + WidgetContentComponent, + WidgetHeaderComponent, + WidgetTasksComponent, + WidgetAuditLogComponent, + DashboardPage, ], providers: [GitIntegrationService], }) diff --git a/src/app/dashboard/dashboard.routing.ts b/src/app/dashboard/dashboard.routing.ts index a982cc7ed..e791fdc0e 100644 --- a/src/app/dashboard/dashboard.routing.ts +++ b/src/app/dashboard/dashboard.routing.ts @@ -9,6 +9,7 @@ import { ProjectsListPage } from './pages/projects-list/projects-list.page'; import { WorkspacesListPage } from './pages/workspaces-list/workspaces-list.page'; import { CreateProjectPage } from './pages/create-project/create-project.page'; import { ProjectPage } from './pages/project/project.page'; +import { DashboardPage } from './pages/dashboard/dashboard.page'; /** * Dashboard routes list @@ -27,10 +28,7 @@ const routes: Routes = [ }, { path: 'dashboard', - component: MockPage, - data: { - image: 'assets/mocks/dashboard.svg', - }, + component: DashboardPage, }, // Workspaces sub route diff --git a/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.html b/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.html new file mode 100644 index 000000000..703fc29af --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.html @@ -0,0 +1,15 @@ + +

Changelog

+
+ +
+

{{ group.project.name }}

+ +
+
+

No updates were made to any of your projects during the audited period.

+
+
+ +
+
diff --git a/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.scss b/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.scss new file mode 100644 index 000000000..82a0db8dc --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.scss @@ -0,0 +1,18 @@ +.project { + padding: 1rem; + background-color: var(--color-secondary-800); + border-radius: var(--border-radius); + + p { + padding-bottom: 1rem; + } +} + +.loader, +.empty { + display: flex; + justify-content: center; + padding: 2rem; + background-color: var(--color-secondary-800); + border-radius: var(--border-radius); +} diff --git a/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.ts b/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.ts new file mode 100644 index 000000000..feae64ff2 --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-audit-log/widget-audit-log.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit } from '@angular/core'; +import { TaskService } from '../../../../../tasks/services/task/task.service'; +import { map, Observable, switchMap, forkJoin, of, EMPTY } from 'rxjs'; +import { MemberService } from '../../../../services/member/member.service'; +import { ProjectMember } from '../../../../interfaces/project-member.interface'; +import { Status } from '../../../../../tasks/interfaces/status.interface'; +import { Task } from '@tasks/interfaces/task.interface'; +import { ProjectService } from '../../../../services/project/project.service'; +import { StatusService } from '../../../../../tasks/services/status/status.service'; +import { Project } from '../../../../interfaces/project.interface'; +import { TaskFilters } from '../../../../../tasks/filters/task.filters'; +import { UserService } from '../../../../../auth/services/user/user.service'; +import { Loader } from '../../../../../_main/classes/loader/loader.class'; +import { withLoader } from '../../../../../_main/operators/loader.operator'; +import { AuditLog } from '../../../../../_main/modules/audit-log/interfaces/audit-log.interface'; + +@Component({ + selector: 'widget-audit-log', + templateUrl: './widget-audit-log.component.html', + styleUrls: ['./widget-audit-log.component.scss'], +}) +export class WidgetAuditLogComponent implements OnInit { + public projects$: Observable< + { + project: Project; + auditLog: AuditLog[]; + }[] + > = EMPTY; + + public columns: Set = new Set(['title', 'status', 'time-tracking']); + + public loader = new Loader(); + + constructor( + private taskService: TaskService, + private memberService: MemberService, + private projectService: ProjectService, + private statusService: StatusService, + private userService: UserService, + ) {} + + ngOnInit() { + this.projects$ = this.loadProjects().pipe(withLoader(this.loader)); + } + + private loadProjects() { + return this.projectService.list().pipe( + switchMap((projects) => { + return forkJoin(projects.map((project) => this.loadProject(project))); + }), + map((projects) => projects.filter((project) => project.auditLog.length > 0)), + ); + } + + private loadProject(project: Project) { + return this.userService.getMyself().pipe( + switchMap((user) => { + return forkJoin({ + project: of(project), + auditLog: this.projectService.auditLog(project.id), + }); + }), + ); + } +} diff --git a/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.html b/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.html new file mode 100644 index 000000000..6dbc74306 --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.html @@ -0,0 +1 @@ + diff --git a/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.scss b/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.scss new file mode 100644 index 000000000..9854b359c --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.scss @@ -0,0 +1,5 @@ +:host { + display: block; + width: 100%; + padding: 1rem; +} diff --git a/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.ts b/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.ts new file mode 100644 index 000000000..45ba1e4bd --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-content/widget-content.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'widget-content', + templateUrl: './widget-content.component.html', + styleUrls: ['./widget-content.component.scss'], +}) +export class WidgetContentComponent {} diff --git a/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.html b/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.html new file mode 100644 index 000000000..df10cf8ee --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.scss b/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.scss new file mode 100644 index 000000000..384c430a7 --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.scss @@ -0,0 +1,11 @@ +:host { + display: block; + width: 100%; + padding: 1rem 1rem 0 1rem; + border-radius: var(--border-radius); +} + +.header-wrapper { + padding: 1rem; + background-color: var(--color-secondary-800); +} diff --git a/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.ts b/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.ts new file mode 100644 index 000000000..545c5833b --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-header/widget-header.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'widget-header', + templateUrl: './widget-header.component.html', + styleUrls: ['./widget-header.component.scss'], +}) +export class WidgetHeaderComponent { + constructor() {} +} diff --git a/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.html b/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.html new file mode 100644 index 000000000..5c3306858 --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.html @@ -0,0 +1,17 @@ + +

Tasks assigned to you

+
+ +
+

{{ group.project.name }}

+ +
+
+

No tasks have been assigned to you at the moment. Feel free to check back later to see if + any new tasks have been added.

+
+
+ +
+
diff --git a/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.scss b/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.scss new file mode 100644 index 000000000..82a0db8dc --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.scss @@ -0,0 +1,18 @@ +.project { + padding: 1rem; + background-color: var(--color-secondary-800); + border-radius: var(--border-radius); + + p { + padding-bottom: 1rem; + } +} + +.loader, +.empty { + display: flex; + justify-content: center; + padding: 2rem; + background-color: var(--color-secondary-800); + border-radius: var(--border-radius); +} diff --git a/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.ts b/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.ts new file mode 100644 index 000000000..d4eed4ecf --- /dev/null +++ b/src/app/dashboard/modules/widgets/components/widget-tasks/widget-tasks.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit } from '@angular/core'; +import { TaskService } from '../../../../../tasks/services/task/task.service'; +import { map, Observable, switchMap, forkJoin, of, EMPTY } from 'rxjs'; +import { MemberService } from '../../../../services/member/member.service'; +import { ProjectMember } from '../../../../interfaces/project-member.interface'; +import { Status } from '../../../../../tasks/interfaces/status.interface'; +import { Task } from '@tasks/interfaces/task.interface'; +import { ProjectService } from '../../../../services/project/project.service'; +import { StatusService } from '../../../../../tasks/services/status/status.service'; +import { Project } from '../../../../interfaces/project.interface'; +import { TaskFilters } from '../../../../../tasks/filters/task.filters'; +import { UserService } from '../../../../../auth/services/user/user.service'; +import { Loader } from '../../../../../_main/classes/loader/loader.class'; +import { withLoader } from '../../../../../_main/operators/loader.operator'; + +@Component({ + selector: 'widget-tasks', + templateUrl: './widget-tasks.component.html', + styleUrls: ['./widget-tasks.component.scss'], +}) +export class WidgetTasksComponent implements OnInit { + public projects$: Observable< + { + project: Project; + members: Map; + statuses: Status[]; + tasks: Task[]; + }[] + > = EMPTY; + + public columns: Set = new Set(['title', 'status', 'time-tracking']); + + public loader = new Loader(); + + constructor( + private taskService: TaskService, + private memberService: MemberService, + private projectService: ProjectService, + private statusService: StatusService, + private userService: UserService, + ) {} + + ngOnInit() { + this.projects$ = this.loadProjects().pipe(withLoader(this.loader)); + } + + private loadProjects() { + return this.projectService.list().pipe( + switchMap((projects) => { + return forkJoin(projects.map((project) => this.loadProject(project))); + }), + map((projects) => projects.filter((project) => project.tasks.length > 0)), + ); + } + + private loadProject(project: Project) { + return this.userService.getMyself().pipe( + switchMap((user) => { + return forkJoin({ + project: of(project), + members: this.memberService.map(project.id), + statuses: this.statusService.list(project.id), + tasks: this.taskService.list(project.id, TaskFilters.ASSIGNEE_ID(user.id)), + }); + }), + ); + } +} diff --git a/src/app/dashboard/pages/create-project/create-project.page.ts b/src/app/dashboard/pages/create-project/create-project.page.ts index 295191d9e..51b9dd52e 100644 --- a/src/app/dashboard/pages/create-project/create-project.page.ts +++ b/src/app/dashboard/pages/create-project/create-project.page.ts @@ -101,7 +101,9 @@ export class CreateProjectPage { stopLoader(this.loader), ) as Observable ).subscribe((project) => { - this.router.navigate(['/', 'projects', project.id]); + this.router.navigate(['/', 'projects', project.id]).then(() => { + location.reload(); + }); }); } diff --git a/src/app/dashboard/pages/dashboard/dashboard.page.html b/src/app/dashboard/pages/dashboard/dashboard.page.html new file mode 100644 index 000000000..7c88d81a4 --- /dev/null +++ b/src/app/dashboard/pages/dashboard/dashboard.page.html @@ -0,0 +1,2 @@ + + diff --git a/src/app/dashboard/pages/dashboard/dashboard.page.scss b/src/app/dashboard/pages/dashboard/dashboard.page.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/dashboard/pages/dashboard/dashboard.page.ts b/src/app/dashboard/pages/dashboard/dashboard.page.ts new file mode 100644 index 000000000..deefb631b --- /dev/null +++ b/src/app/dashboard/pages/dashboard/dashboard.page.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'dashboard-page', + templateUrl: './dashboard.page.html', + styleUrls: ['./dashboard.page.scss'], +}) +export class DashboardPage {} diff --git a/src/app/dashboard/pages/projects-list/projects-list.page.html b/src/app/dashboard/pages/projects-list/projects-list.page.html index d31233633..c32f23d5e 100644 --- a/src/app/dashboard/pages/projects-list/projects-list.page.html +++ b/src/app/dashboard/pages/projects-list/projects-list.page.html @@ -1,48 +1,71 @@ - -
-

{{ (workspace$ | async)?.name }}

- New project -
- - - - - - - - - - - - - - - - - - - - - - - - - - + +
Name {{ row.name }} Statistics - Last update - - - - -
+ diff --git a/src/app/dashboard/pages/projects-list/projects-list.page.scss b/src/app/dashboard/pages/projects-list/projects-list.page.scss index e87d7493d..c7e69ee2a 100644 --- a/src/app/dashboard/pages/projects-list/projects-list.page.scss +++ b/src/app/dashboard/pages/projects-list/projects-list.page.scss @@ -1,4 +1,33 @@ :host { display: block; + height: 100%; padding: 2rem; } + +.loader, +.empty { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +.content { + display: flex; + max-width: 600px; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +h2 { + color: var(--color-text); + font-size: 1.5rem; +} + +p { + color: var(--color-text); + opacity: 0.75; + text-align: center; +} diff --git a/src/app/dashboard/pages/projects-list/projects-list.page.ts b/src/app/dashboard/pages/projects-list/projects-list.page.ts index 90a08ae98..eb917368d 100644 --- a/src/app/dashboard/pages/projects-list/projects-list.page.ts +++ b/src/app/dashboard/pages/projects-list/projects-list.page.ts @@ -7,6 +7,8 @@ import { Workspace } from '../../interfaces/workspace.interface'; import { faPlus } from '@fortawesome/free-solid-svg-icons'; import { DialogService } from '@main/services/dialog/dialog.service'; import { ProjectService } from '../../services/project/project.service'; +import { Loader } from '../../../_main/classes/loader/loader.class'; +import { withLoader, startLoader, stopLoader } from '../../../_main/operators/loader.operator'; /** * Projects list page component @@ -26,6 +28,10 @@ export class ProjectsListPage { /** @ignore */ faPlus = faPlus; + loader = new Loader(); + + workspaceId!: number; + constructor( private activatedRoute: ActivatedRoute, private workspaceService: WorkspaceService, @@ -35,9 +41,15 @@ export class ProjectsListPage { ) { const { workspaceId } = this.activatedRoute.snapshot.params; + this.workspaceId = workspaceId; + + this.loader.markAsPending(); + this.workspace$ = this.workspaceService.get(workspaceId); this.projects$ = this.workspace$.pipe( + startLoader(this.loader), map((workspace) => workspace.projectsWithPrivileges.map((project) => project.project)), + stopLoader(this.loader), ); } diff --git a/src/app/dashboard/pages/workspaces-list/workspaces-list.page.html b/src/app/dashboard/pages/workspaces-list/workspaces-list.page.html index 96ae51edc..133fbd100 100644 --- a/src/app/dashboard/pages/workspaces-list/workspaces-list.page.html +++ b/src/app/dashboard/pages/workspaces-list/workspaces-list.page.html @@ -1,48 +1,71 @@ - -
-

Workspaces

- New workspace -
- - - - - - - - - - - - - - - - - - - - - - - - - - + +
Name {{ row.name }} Statistics - Last update - - - - -
+ \ No newline at end of file diff --git a/src/app/dashboard/pages/workspaces-list/workspaces-list.page.scss b/src/app/dashboard/pages/workspaces-list/workspaces-list.page.scss index e87d7493d..c7e69ee2a 100644 --- a/src/app/dashboard/pages/workspaces-list/workspaces-list.page.scss +++ b/src/app/dashboard/pages/workspaces-list/workspaces-list.page.scss @@ -1,4 +1,33 @@ :host { display: block; + height: 100%; padding: 2rem; } + +.loader, +.empty { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +.content { + display: flex; + max-width: 600px; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +h2 { + color: var(--color-text); + font-size: 1.5rem; +} + +p { + color: var(--color-text); + opacity: 0.75; + text-align: center; +} diff --git a/src/app/dashboard/pages/workspaces-list/workspaces-list.page.ts b/src/app/dashboard/pages/workspaces-list/workspaces-list.page.ts index 05765839d..95c94c72c 100644 --- a/src/app/dashboard/pages/workspaces-list/workspaces-list.page.ts +++ b/src/app/dashboard/pages/workspaces-list/workspaces-list.page.ts @@ -1,12 +1,14 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Pipe } from '@angular/core'; import { Router } from '@angular/router'; import { faPlus } from '@fortawesome/free-solid-svg-icons'; -import { Observable } from 'rxjs'; +import { Observable, of, switchMap } from 'rxjs'; import { Page } from '@main/decorators/page/page.decorator'; import { DialogService } from '@main/services/dialog/dialog.service'; import { Workspace } from '../../interfaces/workspace.interface'; import { ProjectService } from '../../services/project/project.service'; import { WorkspaceService } from '../../services/workspace/workspace.service'; +import { Loader } from '../../../_main/classes/loader/loader.class'; +import { withLoader, startLoader, stopLoader } from '../../../_main/operators/loader.operator'; /** * Workspaces list page component. @@ -26,14 +28,11 @@ export class WorkspacesListPage implements OnInit { */ constructor( private workspaceService: WorkspaceService, - private projectService: ProjectService, private dialogService: DialogService, private router: Router, ) {} - /** - * Plus icon to display on the add button - */ + /** @ignore */ public faPlus = faPlus; /** @@ -41,9 +40,8 @@ export class WorkspacesListPage implements OnInit { */ public workspaces$?: Observable; - /** - * Lifecycle hook to load workspaces at the start of the page. - */ + public loader = new Loader(); + ngOnInit() { this.loadWorkspaces(); } @@ -52,7 +50,12 @@ export class WorkspacesListPage implements OnInit { * Loads the workspaces list from the workspace service. */ loadWorkspaces() { - this.workspaces$ = this.workspaceService.list(); + this.loader.markAsPending(); + this.workspaces$ = of(null).pipe( + startLoader(this.loader), + switchMap(() => this.workspaceService.list()), + stopLoader(this.loader), + ); } /** diff --git a/src/app/messages/messages.module.ts b/src/app/messages/messages.module.ts index 7794334d0..51e399ce7 100644 --- a/src/app/messages/messages.module.ts +++ b/src/app/messages/messages.module.ts @@ -7,6 +7,7 @@ import { MessengerPage } from './pages/messenger/messenger.page'; import { MainModule } from '@main/_main.module'; import { MessageComponent } from './components/message/message.component'; import { MessageGroupComponent } from './components/message-group/message-group.component'; +import { MessagesNoIntegration } from './pages/messages-no-integration/messages-no-integration.page'; /** Communication module with all messaging tools and pages */ @NgModule({ @@ -17,6 +18,7 @@ import { MessageGroupComponent } from './components/message-group/message-group. MessengerPage, MessageComponent, MessageGroupComponent, + MessagesNoIntegration, ], exports: [MessageComponent], }) diff --git a/src/app/messages/messages.routing.ts b/src/app/messages/messages.routing.ts index 1ac47eb8c..313ec8e51 100644 --- a/src/app/messages/messages.routing.ts +++ b/src/app/messages/messages.routing.ts @@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; import { ConversationPage } from './pages/conversation/conversation.page'; import { MessengerSummaryPage } from './pages/messenger-summary/messenger-summary.page'; import { MessengerPage } from './pages/messenger/messenger.page'; +import { MessagesNoIntegration } from './pages/messages-no-integration/messages-no-integration.page'; /** * Messages routes list @@ -17,6 +18,10 @@ const routes: Routes = [ pathMatch: 'full', component: MessengerSummaryPage, }, + { + path: 'no-integration', + component: MessagesNoIntegration, + }, { path: ':integrationId/:channelId', component: ConversationPage, diff --git a/src/app/messages/pages/messages-no-integration/messages-no-integration.page.html b/src/app/messages/pages/messages-no-integration/messages-no-integration.page.html new file mode 100644 index 000000000..e83059956 --- /dev/null +++ b/src/app/messages/pages/messages-no-integration/messages-no-integration.page.html @@ -0,0 +1,10 @@ +
+

+ No Slack integration found +

+

+ Messages module require Slack integration to work. To connect your account with Slack you need to add it in settings + page. +

+ Go to Settings +
diff --git a/src/app/messages/pages/messages-no-integration/messages-no-integration.page.scss b/src/app/messages/pages/messages-no-integration/messages-no-integration.page.scss new file mode 100644 index 000000000..573b2eff8 --- /dev/null +++ b/src/app/messages/pages/messages-no-integration/messages-no-integration.page.scss @@ -0,0 +1,26 @@ +:host { + display: flex; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +} + +.content { + display: flex; + max-width: 600px; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +h2 { + color: var(--color-text); + font-size: 1.5rem; +} + +p { + color: var(--color-text); + opacity: 0.75; + text-align: center; +} diff --git a/src/app/messages/pages/messages-no-integration/messages-no-integration.page.ts b/src/app/messages/pages/messages-no-integration/messages-no-integration.page.ts new file mode 100644 index 000000000..0da539bef --- /dev/null +++ b/src/app/messages/pages/messages-no-integration/messages-no-integration.page.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'messages-no-integration', + templateUrl: './messages-no-integration.page.html', + styleUrls: ['./messages-no-integration.page.scss'], +}) +export class MessagesNoIntegration {} diff --git a/src/app/tasks/components/task-list/task-list.component.html b/src/app/tasks/components/task-list/task-list.component.html index 755940d0b..82a104aaf 100644 --- a/src/app/tasks/components/task-list/task-list.component.html +++ b/src/app/tasks/components/task-list/task-list.component.html @@ -2,12 +2,12 @@
-
Title
-
Status
-
Assignee
-
Time tracking
-
Deadline
-
Story points
+
Title
+
Status
+
Assignee
+
Time tracking
+
Deadline
+
Story points
@@ -16,7 +16,7 @@
+ [members]="members" [statusList]="statusList" [columns]="columns">