diff --git a/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.html b/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.html
index 842845c4ec2..c7246e2ec95 100644
--- a/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.html
+++ b/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.html
@@ -1,13 +1,11 @@
diff --git a/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.ts b/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.ts
index 7d2a3a6a533..b3daa54972b 100644
--- a/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.ts
+++ b/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deal-form/pipeline-deal-form.component.ts
@@ -1,14 +1,14 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
+import { ActivatedRoute, Data, Router } from '@angular/router';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
+import { catchError, map, Observable, of, switchMap, tap } from 'rxjs';
import { filter } from 'rxjs/operators';
-import { Subject } from 'rxjs';
-import { IPipeline, IContact } from '@gauzy/contracts';
-import { AppStore, Store } from '@gauzy/ui-core/common';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { TranslateService } from '@ngx-translate/core';
+import { IPipeline, IContact, IOrganization, IDeal, IPagination } from '@gauzy/contracts';
+import { distinctUntilChange, Store } from '@gauzy/ui-core/common';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
-import { DealsService, OrganizationContactService, PipelinesService, ToastrService } from '@gauzy/ui-core/core';
+import { DealsService, ErrorHandlingService, OrganizationContactService, ToastrService } from '@gauzy/ui-core/core';
@UntilDestroy({ checkProperties: true })
@Component({
@@ -17,184 +17,183 @@ import { DealsService, OrganizationContactService, PipelinesService, ToastrServi
styleUrls: ['./pipeline-deal-form.component.scss']
})
export class PipelineDealFormComponent extends TranslationBaseComponent implements OnInit, OnDestroy {
- form: UntypedFormGroup;
- pipeline: IPipeline;
- clients: IContact[];
- selectedClient: IContact;
- probabilities = [0, 1, 2, 3, 4, 5];
- selectedProbability: number;
- mode: 'CREATE' | 'EDIT' = 'CREATE';
- dealId: string;
- pipelineId: string;
-
- private readonly $akitaPreUpdate: AppStore['akitaPreUpdate'];
- private _ngDestroy$ = new Subject();
- private organizationId: string;
- private tenantId: string;
+ public selectedClient: IContact;
+ public probabilities = [0, 1, 2, 3, 4, 5];
+ public selectedProbability: number;
+ public organization: IOrganization;
+ public deal: IDeal;
+ public deal$: Observable;
+ public pipeline: IPipeline;
+ public pipeline$: Observable;
+ public clients: IContact[] = [];
+ public clients$: Observable;
+
+ // Form Builder
+ public form: UntypedFormGroup = PipelineDealFormComponent.buildForm(this._fb);
+ static buildForm(fb: UntypedFormBuilder): UntypedFormGroup {
+ return fb.group({
+ stageId: [null, Validators.required],
+ title: [null, Validators.required],
+ clientId: [null],
+ probability: [null, Validators.required]
+ });
+ }
constructor(
public readonly translateService: TranslateService,
- private readonly router: Router,
- private readonly fb: UntypedFormBuilder,
- private readonly appStore: AppStore,
- private readonly store: Store,
- private readonly dealsService: DealsService,
- private readonly activatedRoute: ActivatedRoute,
- private readonly pipelinesService: PipelinesService,
- private readonly clientsService: OrganizationContactService,
- private readonly toastrService: ToastrService
+ private readonly _router: Router,
+ private readonly _fb: UntypedFormBuilder,
+ private readonly _store: Store,
+ private readonly _dealsService: DealsService,
+ private readonly _activatedRoute: ActivatedRoute,
+ private readonly _clientsService: OrganizationContactService,
+ private readonly _toastrService: ToastrService,
+ private readonly _errorHandlingService: ErrorHandlingService
) {
super(translateService);
-
- this.$akitaPreUpdate = appStore.akitaPreUpdate;
-
- appStore.akitaPreUpdate = (previous, next) => {
- if (previous.user !== next.user) {
- setTimeout(() => this.form.patchValue({ createdByUserId: next.user.id }));
- }
-
- return this.$akitaPreUpdate(previous, next);
- };
}
ngOnInit() {
- this._initializeForm();
- this.activatedRoute.params
- .pipe(
- filter((params) => !!params),
- untilDestroyed(this)
- )
- .subscribe(async ({ pipelineId, dealId }) => {
- this.form.disable();
- if (pipelineId) {
- this.pipelineId = pipelineId;
- this.mode = 'EDIT';
- }
- if (dealId) {
- this.dealId = dealId;
- }
- this.form.enable();
- });
- this.store.selectedOrganization$
- .pipe(
- filter((organization) => !!organization),
- untilDestroyed(this)
- )
- .subscribe(async (org) => {
- this.organizationId = org.id;
- this.tenantId = this.store.user.tenantId;
-
- await this.getOrganizationContact();
-
- if (this.pipelineId) {
- await this.getPipelines();
- }
- if (this.dealId) {
- await this.getDeal();
- }
- });
+ // Setting up the organization$ observable pipeline
+ this.clients$ = this._store.selectedOrganization$.pipe(
+ // Ensure only distinct values are emitted
+ distinctUntilChange(),
+ // Exclude falsy values from the emitted values
+ filter((organization: IOrganization) => !!organization),
+ // Tap operator for side effects - setting the organization property
+ tap((organization: IOrganization) => (this.organization = organization)),
+ // Switch to route data stream once organization is confirmed
+ switchMap(() => {
+ // Extract organization properties
+ const { id: organizationId, tenantId } = this.organization;
+ // Fetch contacts
+ return this._clientsService.getAll([], {
+ organizationId,
+ tenantId
+ });
+ }),
+ // Map the contacts to the clients property
+ map(({ items }: IPagination) => items),
+ // Handle errors
+ catchError((error) => {
+ console.error('Error fetching organization contacts:', error);
+ // Handle and log errors
+ this._errorHandlingService.handleError(error);
+ return of([]);
+ }),
+ // Handle component lifecycle to avoid memory leaks
+ untilDestroyed(this)
+ );
+ this.pipeline$ = this._activatedRoute.params.pipe(
+ // Filter for the presence of pipelineId in route params
+ filter(({ pipelineId }) => !!pipelineId),
+ // Switch to route data stream once pipelineId is confirmed
+ switchMap(() => this._activatedRoute.data),
+ // Exclude falsy values from the emitted values
+ filter(({ pipeline }: Data) => !!pipeline),
+ // Map the pipeline to the pipeline property
+ map(({ pipeline }: Data) => pipeline),
+ // Tap operator for side effects - setting the form property
+ tap((pipeline: IPipeline) => {
+ this.pipeline = pipeline;
+ this.form.patchValue({ stageId: this.pipeline.stages[0]?.id });
+ }),
+ // Handle component lifecycle to avoid memory leaks
+ untilDestroyed(this)
+ );
+ this.deal$ = this._activatedRoute.params.pipe(
+ // Filter for the presence of dealId in route params
+ filter(({ dealId }) => !!dealId),
+ // Switch to route data stream once dealId is confirmed
+ switchMap(() => this._activatedRoute.data),
+ // Exclude falsy values from the emitted values
+ filter(({ deal }: Data) => !!deal),
+ // Map the deal to the deal property
+ map(({ deal }: Data) => deal),
+ // Tap operator for side effects - setting the form property
+ tap((deal: IDeal) => {
+ this.deal = deal;
+ this.patchFormValue(deal);
+ }),
+ // Handle component lifecycle to avoid memory leaks
+ untilDestroyed(this)
+ );
}
- private _initializeForm() {
- this.form = this.fb.group({
- createdByUserId: [null, Validators.required],
- stageId: [null, Validators.required],
- title: [null, Validators.required],
- clientId: [null],
- probability: [null, Validators.required]
- });
+ /**
+ * Patch form values with the deal data
+ *
+ * @param deal The deal object containing data to patch into the form
+ */
+ patchFormValue(deal: IDeal) {
+ const { title, stageId, createdBy, probability, clientId } = deal;
this.form.patchValue({
- createdByUserId: this.appStore.getValue().user?.id
+ title,
+ stageId,
+ createdBy,
+ probability,
+ clientId
});
+ this.selectedProbability = probability;
}
- async getPipelines() {
- const { tenantId } = this;
- await this.pipelinesService
- .getAll(['stages'], {
- id: this.pipelineId,
- tenantId
- })
- .then(({ items: [value] }) => (this.pipeline = value));
+ /**
+ * Submits the form data for creating or updating a deal.
+ *
+ * This method handles the submission of form data for either creating a new deal
+ * or updating an existing one. It also manages form state (disable/enable) and
+ * displays success notifications.
+ *
+ * @returns {Promise} A promise that resolves when the form submission is complete.
+ */
+ public async onSubmit(): Promise {
+ const { organization, form } = this;
- this.form.patchValue({ stageId: this.pipeline.stages[0]?.id });
- }
+ // If no organization is selected, do not proceed
+ if (!organization) {
+ return;
+ }
- async getDeal() {
- const { tenantId } = this;
- await this.dealsService
- .getOne(this.dealId, { tenantId }, ['client'])
- .then(({ title, stageId, createdBy, probability, clientId, client }) => {
- this.form.patchValue({
- title,
- stageId,
- createdBy,
- probability,
- clientId
- });
- this.selectedProbability = probability;
- });
- }
+ // Extract organizationId and tenantId from the selected organization
+ const { id: organizationId, tenantId } = organization;
- async getOrganizationContact() {
- await this.clientsService
- .getAll([], {
- organizationId: this.organizationId,
- tenantId: this.tenantId
- })
- .then((res) => (this.clients = res.items));
- }
+ // Merge the form values with organizationId and tenantId
+ const value = { ...form.value, organizationId, tenantId };
- public async onSubmit(): Promise {
- const {
- dealId,
- activatedRoute: relativeTo,
- form: { value }
- } = this;
-
- this.form.disable();
- await (this.dealId
- ? this.dealsService.update(
- this.dealId,
- Object.assign(
- {
- organizationId: this.organizationId,
- tenantId: this.tenantId
- },
- value
- )
- )
- : this.dealsService.create(
- Object.assign(
- {
- organizationId: this.organizationId,
- tenantId: this.tenantId
- },
- value
- )
- )
- )
- .then(() => {
- if (this.dealId) {
- this.toastrService.success('PIPELINE_DEALS_PAGE.DEAL_EDITED', {
- name: value.title
- });
- } else {
- this.toastrService.success('PIPELINE_DEALS_PAGE.DEAL_ADDED', {
- name: value.title
- });
- }
- this.router.navigate([dealId ? '../..' : '..'], { relativeTo });
- })
- .catch(() => this.form.enable());
+ // Disable the form to prevent further input during submission
+ form.disable();
+
+ try {
+ // Determine whether to create a new deal or update an existing one
+ if (this.deal) {
+ await this._dealsService.update(this.deal?.id, value);
+ } else {
+ await this._dealsService.create(value);
+ }
+
+ // Determine the success message based on whether it's a create or update operation
+ const successMessage = this.deal?.id ? 'PIPELINE_DEALS_PAGE.DEAL_EDITED' : 'PIPELINE_DEALS_PAGE.DEAL_ADDED';
+
+ // Display a success notification with the deal title
+ this._toastrService.success(successMessage, { name: value.title });
+
+ // Navigate to the appropriate route after successful submission
+ this._router.navigate([this.deal?.id ? '../..' : '..'], { relativeTo: this._activatedRoute });
+ } catch (error) {
+ // Handle and log errors
+ this._errorHandlingService.handleError(error);
+ } finally {
+ // If an error occurs, re-enable the form for further input
+ form.enable();
+ }
}
+ /**
+ * Cancels the form submission.
+ */
cancel() {
window.history.back();
}
- ngOnDestroy() {
- this._ngDestroy$.next();
- this._ngDestroy$.complete();
- }
+ ngOnDestroy() {}
}
diff --git a/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deals.component.html b/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deals.component.html
index 04c46ec2722..0903a683cad 100644
--- a/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deals.component.html
+++ b/apps/gauzy/src/app/pages/pipelines/pipeline-deals/pipeline-deals.component.html
@@ -1,15 +1,9 @@
-