From 513683f679936df016cf8e3f1563afa637501cd5 Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Tue, 15 Oct 2024 19:00:46 +0200 Subject: [PATCH 1/3] [Fix] Task Status Not Pre-filled During Edit, Causing Error on Save Without Re-selecting Status --- .../task-status-select.component.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts index bec127f29f3..cfbc52e9d2a 100644 --- a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts +++ b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts @@ -198,7 +198,16 @@ export class TaskStatusSelectComponent extends TranslationBaseComponent implemen }) .pipe( map(({ items, total }: IPagination) => (total > 0 ? items : this._statuses)), - tap((statuses: ITaskStatus[]) => this.statuses$.next(statuses)), + tap((statuses: ITaskStatus[]) => { + this.statuses$.next(statuses); + if (!this.status) { + const defaultStatus = statuses.find((status) => status.name === TaskStatusEnum.OPEN); + if (defaultStatus) { + this.status = defaultStatus; + this.onChange(defaultStatus); + } + } + }), untilDestroyed(this) ) .subscribe(); From 53799b12743825c387e8e379c65cc078f704248e Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 17 Oct 2024 11:33:43 +0530 Subject: [PATCH 2/3] fix: optimize "Task Status" selector usability --- .../task-status-select.component.ts | 217 +++++++++--------- .../task-status-select.module.ts | 12 +- 2 files changed, 108 insertions(+), 121 deletions(-) diff --git a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts index cfbc52e9d2a..f9fac8b11c8 100644 --- a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts +++ b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts @@ -1,20 +1,11 @@ import { AfterViewInit, Component, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { combineLatest, debounceTime, firstValueFrom, Subject } from 'rxjs'; -import { filter, map, tap } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, debounceTime, filter, firstValueFrom, map, Subject, tap } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; -import { - IOrganization, - IOrganizationProject, - IPagination, - ITaskStatus, - ITaskStatusFindInput, - TaskStatusEnum -} from '@gauzy/contracts'; -import { distinctUntilChange, sluggable } from '@gauzy/ui-core/common'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { Store, TaskStatusesService, ToastrService } from '@gauzy/ui-core/core'; +import { ID, IOrganization, IPagination, ITaskStatus, ITaskStatusFindInput, TaskStatusEnum } from '@gauzy/contracts'; +import { distinctUntilChange, sluggable } from '@gauzy/ui-core/common'; +import { ErrorHandlingService, Store, TaskStatusesService } from '@gauzy/ui-core/core'; import { TranslationBaseComponent } from '@gauzy/ui-core/i18n'; @UntilDestroy({ checkProperties: true }) @@ -30,107 +21,88 @@ import { TranslationBaseComponent } from '@gauzy/ui-core/i18n'; ] }) export class TaskStatusSelectComponent extends TranslationBaseComponent implements AfterViewInit, OnInit, OnDestroy { + public organization: IOrganization; private subject$: Subject = new Subject(); + /** - * Default global task statuses + * A BehaviorSubject to store and emit the latest list of task statuses. */ - private _statuses: Array<{ name: string; value: TaskStatusEnum & any }> = [ - { - name: TaskStatusEnum.OPEN, - value: sluggable(TaskStatusEnum.OPEN) - }, - { - name: TaskStatusEnum.IN_PROGRESS, - value: sluggable(TaskStatusEnum.IN_PROGRESS) - }, - { - name: TaskStatusEnum.READY_FOR_REVIEW, - value: sluggable(TaskStatusEnum.READY_FOR_REVIEW) - }, - { - name: TaskStatusEnum.IN_REVIEW, - value: sluggable(TaskStatusEnum.IN_REVIEW) - }, - { - name: TaskStatusEnum.BLOCKED, - value: sluggable(TaskStatusEnum.BLOCKED) - }, - { - name: TaskStatusEnum.COMPLETED, - value: sluggable(TaskStatusEnum.COMPLETED) - } - ]; - public organization: IOrganization; public statuses$: BehaviorSubject = new BehaviorSubject([]); + + /** + * Predefined task statuses with names and sluggified values. + */ + private _statuses: Array<{ name: string; value: string }> = [ + { name: TaskStatusEnum.OPEN, value: sluggable(TaskStatusEnum.OPEN) }, + { name: TaskStatusEnum.IN_PROGRESS, value: sluggable(TaskStatusEnum.IN_PROGRESS) }, + { name: TaskStatusEnum.READY_FOR_REVIEW, value: sluggable(TaskStatusEnum.READY_FOR_REVIEW) }, + { name: TaskStatusEnum.IN_REVIEW, value: sluggable(TaskStatusEnum.IN_REVIEW) }, + { name: TaskStatusEnum.BLOCKED, value: sluggable(TaskStatusEnum.BLOCKED) }, + { name: TaskStatusEnum.COMPLETED, value: sluggable(TaskStatusEnum.COMPLETED) } + ]; + + /** + * EventEmitter to notify when a status is selected or changed. + */ @Output() onChanged = new EventEmitter(); constructor( public readonly translateService: TranslateService, - public readonly store: Store, - public readonly taskStatusesService: TaskStatusesService, - private readonly toastrService: ToastrService + private readonly _store: Store, + private readonly _taskStatusesService: TaskStatusesService, + private readonly _errorHandlingService: ErrorHandlingService ) { super(translateService); } - /* - * Getter & Setter for selected organization project + /** + * */ - private _projectId: IOrganizationProject['id']; + @Input() addTag: boolean = true; - get projectId(): IOrganizationProject['id'] { - return this._projectId; - } - - @Input() set projectId(value: IOrganizationProject['id']) { - this._projectId = value; - this.subject$.next(true); - } - - /* - * Getter & Setter for dynamic add tag option + /** + * The placeholder text to be displayed in the project selector. + * Provides guidance to the user on what action to take or what information to provide. + * */ - private _addTag: boolean = true; - - get addTag(): boolean { - return this._addTag; - } - - @Input() set addTag(value: boolean) { - this._addTag = value; - } + @Input() placeholder: string | null = null; /* - * Getter & Setter for dynamic placeholder + * Getter and Setter for the selected organization project ID. + * The setter updates the private _projectId and notifies any observers of the change. */ - private _placeholder: string; - - get placeholder(): string { - return this._placeholder; + private _projectId: ID; + @Input() set projectId(value: ID) { + this._projectId = value; + this.subject$.next(true); // Notify subscribers that the project ID has changed } - @Input() set placeholder(value: string) { - this._placeholder = value; + get projectId(): ID { + return this._projectId; } /* * Getter & Setter for status */ private _status: ITaskStatus; - get status(): ITaskStatus { return this._status; } - set status(val: ITaskStatus) { this._status = val; - this.onChange(val); - this.onTouched(val); + this.onChange(val); // Notify form control value change + this.onTouched(); // Mark as touched in form control } - onChange: any = () => {}; + /** + * Callback function to notify changes in the form control. + */ + private onChange: (value: ITaskStatus) => void = () => {}; - onTouched: any = () => {}; + /** + * Callback function to notify touch events in the form control. + */ + private onTouched: () => void = () => {}; ngOnInit(): void { this.subject$ @@ -143,8 +115,8 @@ export class TaskStatusSelectComponent extends TranslationBaseComponent implemen } ngAfterViewInit(): void { - const storeOrganization$ = this.store.selectedOrganization$; - const storeProject$ = this.store.selectedProject$; + const storeOrganization$ = this._store.selectedOrganization$; + const storeProject$ = this._store.selectedProject$; combineLatest([storeOrganization$, storeProject$]) .pipe( distinctUntilChange(), @@ -159,47 +131,69 @@ export class TaskStatusSelectComponent extends TranslationBaseComponent implemen .subscribe(); } - writeValue(value: ITaskStatus) { + /** + * Updates the status value for the component. + * + * @param value - The task status to be written to the component. + */ + writeValue(value: ITaskStatus): void { this.status = value; } - registerOnChange(fn: (rating: number) => void): void { + /** + * Registers a callback function to be called when the status changes. + * + * @param fn - The function that is triggered on status change. + */ + registerOnChange(fn: (status: ITaskStatus) => void): void { this.onChange = fn; } + /** + * Registers a callback function to be called when the component is touched. + * + * @param fn - The function that is triggered when the component is touched. + */ registerOnTouched(fn: () => void): void { this.onTouched = fn; } - selectStatus(status: ITaskStatus) { + /** + * Emits the selected status when a task status is chosen. + * + * @param status - The selected task status. + */ + selectStatus(status: ITaskStatus): void { this.onChanged.emit(status); } /** - * Get task statuses based organization & project + * Retrieves task statuses based on the organization and project. + * If a project ID is available, it filters statuses accordingly. + * Emits the list of statuses and sets the default status if none is selected. */ - getStatuses() { + getStatuses(): void { if (!this.organization) { return; } - const { tenantId } = this.store.user; - const { id: organizationId } = this.organization; + const { id: organizationId, tenantId } = this.organization; - this.taskStatusesService + // Fetch task statuses from the service + this._taskStatusesService .get({ tenantId, organizationId, - ...(this.projectId - ? { - projectId: this.projectId - } - : {}) + ...(this.projectId ? { projectId: this.projectId } : {}) }) .pipe( + // Map the response to either the fetched statuses or a default set map(({ items, total }: IPagination) => (total > 0 ? items : this._statuses)), + // Update the observable with the fetched statuses tap((statuses: ITaskStatus[]) => { this.statuses$.next(statuses); + + // Set default status if no status is currently selected if (!this.status) { const defaultStatus = statuses.find((status) => status.name === TaskStatusEnum.OPEN); if (defaultStatus) { @@ -208,39 +202,40 @@ export class TaskStatusSelectComponent extends TranslationBaseComponent implemen } } }), - untilDestroyed(this) + untilDestroyed(this) // Clean up the subscription when component is destroyed ) .subscribe(); } /** - * Create new status from ng-select tag + * Creates a new task status from the ng-select input. * - * @param name - * @returns + * @param name - The name of the new status to be created. + * @returns A promise that resolves when the status is successfully created. */ - createNew = async (name: string) => { + createNew = async (name: string): Promise => { if (!this.organization) { return; } + try { - const { tenantId } = this.store.user; - const { id: organizationId } = this.organization; + const { id: organizationId, tenantId } = this.organization; - const source = this.taskStatusesService.create({ + // Prepare the task status payload + const payload = { tenantId, organizationId, name, - ...(this.projectId - ? { - projectId: this.projectId - } - : {}) - }); - await firstValueFrom(source); + ...(this.projectId ? { projectId: this.projectId } : {}) + }; + + // Create the new task status and wait for completion + await firstValueFrom(this._taskStatusesService.create(payload)); } catch (error) { - this.toastrService.error(error); + console.error('Error while creating new task status:', error); + this._errorHandlingService.handleError(error); } finally { + // Notify observers after creation attempt this.subject$.next(true); } }; diff --git a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.module.ts b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.module.ts index 607cee0ec68..c901bd71a99 100644 --- a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.module.ts +++ b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.module.ts @@ -2,21 +2,13 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { NgSelectModule } from '@ng-select/ng-select'; -import { TaskStatusSelectComponent } from './task-status-select.component'; import { TranslateModule } from '@ngx-translate/core'; import { TaskStatusesService } from '@gauzy/ui-core/core'; -import { PipesModule } from '../../pipes/pipes.module'; import { TaskBadgeViewComponentModule } from '../task-badge-view/task-badge-view.module'; +import { TaskStatusSelectComponent } from './task-status-select.component'; @NgModule({ - imports: [ - CommonModule, - FormsModule, - NgSelectModule, - TranslateModule.forChild(), - PipesModule, - TaskBadgeViewComponentModule - ], + imports: [CommonModule, FormsModule, NgSelectModule, TranslateModule.forChild(), TaskBadgeViewComponentModule], declarations: [TaskStatusSelectComponent], exports: [TaskStatusSelectComponent], providers: [TaskStatusesService] From d8af5c6c6259893e5d83bfc12bfc39827b60a823 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Thu, 17 Oct 2024 11:35:55 +0530 Subject: [PATCH 3/3] fix(cspell): typo spelling :-) --- .../tasks/task-status-select/task-status-select.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts index f9fac8b11c8..27414654b69 100644 --- a/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts +++ b/packages/ui-core/shared/src/lib/tasks/task-status-select/task-status-select.component.ts @@ -30,7 +30,7 @@ export class TaskStatusSelectComponent extends TranslationBaseComponent implemen public statuses$: BehaviorSubject = new BehaviorSubject([]); /** - * Predefined task statuses with names and sluggified values. + * Predefined task statuses with names and sluggable values. */ private _statuses: Array<{ name: string; value: string }> = [ { name: TaskStatusEnum.OPEN, value: sluggable(TaskStatusEnum.OPEN) },