src/app/core/admin/admin-entity/admin-entity-general-settings/conditional-color-config/conditional-color-config.component.ts

Description

A form control for configuring conditional colors based on entity fields.

Extends

CustomFormControlDirective< string | ColorMapping[] >

Example

Metadata

Relationships

Depends on

Index

Properties
Methods
Inputs
Outputs
Accessors

Inputs

entityConstructor
Type : EntityConstructor
Required :  true
isConditionalMode
Type : boolean
Default value : false
aria-describedby
Type : string
disabled
Type : boolean
ngControl
Type : any
Default value : inject(NgControl, { optional: true, self: true })
placeholder
Type : string
required
Type : boolean
value
Type : T

Outputs

isConditionalModeChange
Type : boolean
valueChange
Type : EventEmitter

Methods

addConditionalColorSection
addConditionalColorSection()

Add a new conditional color section

Returns : void
deleteConditionalColorSection
deleteConditionalColorSection(sectionIndex: number)

Delete a conditional color section

Parameters :
Name Type Optional
sectionIndex number No
Returns : void
onConditionChange
onConditionChange(section: ColorMapping, updatedConditions: any)

Handle any change in conditional sections that requires value update

Parameters :
Name Type Optional
section ColorMapping No
updatedConditions any No
Returns : void
onStaticColorChange
onStaticColorChange(newColor: string)

Update the static/default color

Parameters :
Name Type Optional
newColor string No
Returns : void
updateConditionalSectionColor
updateConditionalSectionColor(sectionIndex: number, newColor: string)

Update the color for a conditional section

Parameters :
Name Type Optional
sectionIndex number No
newColor string No
Returns : void
blur
blur()
Returns : void
focus
focus()
Returns : void
onContainerClick
onContainerClick(event: MouseEvent)
Parameters :
Name Type Optional
event MouseEvent No
Returns : void
registerOnChange
registerOnChange(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
registerOnTouched
registerOnTouched(fn: any)
Parameters :
Name Type Optional
fn any No
Returns : void
setDescribedByIds
setDescribedByIds(ids: string[])
Parameters :
Name Type Optional
ids string[] No
Returns : void
setDisabledState
setDisabledState(isDisabled: boolean)
Parameters :
Name Type Optional
isDisabled boolean No
Returns : void
writeValue
writeValue(value: T, notifyFormControl: unknown)

Implementation for Angular ControlValueAccessor interface that links the form control value to the component value

Parameters :
Name Type Optional Default value Description
value T No

The new value to set

notifyFormControl unknown No false

Whether to notify the FormControl of this change (for internal updates)

Returns : void

Properties

controlType
Type : string
Default value : "custom-control"
elementRef
Type : unknown
Default value : inject<ElementRef<HTMLElement>>(ElementRef)
Readonly enabled
Type : Signal<boolean>
Default value : computed(() => !this._disabled())

Whether the control is currently enabled, as a signal (tracks disabled).

errorStateMatcher
Type : unknown
Default value : inject(ErrorStateMatcher)
id
Type : unknown
Default value : `custom-form-control-${CustomFormControlDirective.nextId++}`
Static nextId
Type : number
Default value : 0
onChange
Type : unknown
Default value : () => {...}
onTouched
Type : unknown
Default value : () => {...}
parentForm
Type : unknown
Default value : inject(NgForm, { optional: true })
parentFormGroup
Type : unknown
Default value : inject(FormGroupDirective, { optional: true })
stateChanges
Type : unknown
Default value : new Subject<void>()
Readonly valueSignal
Type : Signal<T>
Default value : computed(() => this._value())

The current value of the control as a signal. Authoritative in both modes: it reflects the bound FormControl (synced in ngDoCheck) as well as [(value)] / writeValue updates.

Accessors

staticColor
getstaticColor()
conditionalColorSections
getconditionalColorSections()
import {
  Component,
  input,
  output,
  ChangeDetectionStrategy,
} from "@angular/core";
import {
  MatFormFieldModule,
  MatFormFieldControl,
} from "@angular/material/form-field";
import { MatButtonModule } from "@angular/material/button";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { MatTooltipModule } from "@angular/material/tooltip";
import { CustomFormControlDirective } from "app/core/common-components/basic-autocomplete/custom-form-control.directive";
import { ColorMapping, EntityConstructor } from "app/core/entity/model/entity";
import { ColorInputComponent } from "#src/app/core/common-components/color-input/color-input.component";
import { ConditionalColorSectionComponent } from "./conditional-color-section/conditional-color-section.component";

/**
 * A form control for configuring conditional colors based on entity fields.
 */
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-conditional-color-config",
  templateUrl: "./conditional-color-config.component.html",
  styleUrls: ["./conditional-color-config.component.scss"],
  providers: [
    {
      provide: MatFormFieldControl,
      useExisting: ConditionalColorConfigComponent,
    },
  ],
  imports: [
    MatFormFieldModule,
    MatButtonModule,
    FontAwesomeModule,
    MatTooltipModule,
    ColorInputComponent,
    ConditionalColorSectionComponent,
  ],
})
export class ConditionalColorConfigComponent extends CustomFormControlDirective<
  string | ColorMapping[]
> {
  entityConstructor = input.required<EntityConstructor>();
  isConditionalMode = input<boolean>(false);
  isConditionalModeChange = output<boolean>();

  // Cached values to avoid recalculating in template
  get staticColor(): string {
    if (typeof this.value === "string") return this.value;
    if (!Array.isArray(this.value)) return "";
    return (
      this.value.find((m) => !Object.keys(m.condition || {}).length)?.color ||
      ""
    );
  }

  get conditionalColorSections(): ColorMapping[] {
    if (!Array.isArray(this.value)) return [];
    return this.value.filter((m) => Object.keys(m.condition || {}).length > 0);
  }

  /**
   * Add a new conditional color section
   */
  addConditionalColorSection(): void {
    // Enable conditional mode if not already enabled
    if (!this.isConditionalMode()) {
      this.isConditionalModeChange.emit(true);
    }

    if (!Array.isArray(this.value)) {
      this.value = [{ condition: {}, color: this.staticColor }];
    }

    // Add new conditional section with one empty condition to start
    const newSection: ColorMapping = {
      condition: { $or: [{}] },
      color: "",
    };

    this.value = [...this.value, newSection];
    this.onChange(this.value);
  }

  /**
   * Delete a conditional color section
   */
  deleteConditionalColorSection(sectionIndex: number): void {
    if (!Array.isArray(this.value)) return;

    const conditionalSections = this.conditionalColorSections;
    if (sectionIndex < 0 || sectionIndex >= conditionalSections.length) return;

    // Remove the section
    const staticMapping = this.value.find(
      (m) => !Object.keys(m.condition || {}).length,
    );
    const remainingSections = conditionalSections.filter(
      (_, index) => index !== sectionIndex,
    );

    this.value = staticMapping
      ? [staticMapping, ...remainingSections]
      : remainingSections;
    this.onChange(this.value);
  }

  /**
   * Update the color for a conditional section
   */
  updateConditionalSectionColor(sectionIndex: number, newColor: string): void {
    const conditionalSections = this.conditionalColorSections;
    if (sectionIndex < 0 || sectionIndex >= conditionalSections.length) return;

    conditionalSections[sectionIndex].color = newColor;
    this.updateValue();
  }

  /**
   * Handle any change in conditional sections that requires value update
   */
  onConditionChange(section: ColorMapping, updatedConditions: any): void {
    if (!Array.isArray(this.value)) return;

    const sectionIndex = this.value.findIndex(
      (candidate) => candidate === section,
    );
    if (sectionIndex < 0) return;

    this.value = this.value.map((candidate, index) =>
      index === sectionIndex
        ? { ...candidate, condition: updatedConditions }
        : candidate,
    );

    this.updateValue();
  }

  /**
   * Update the static/default color
   */
  onStaticColorChange(newColor: string): void {
    if (typeof this.value === "string") {
      this.value = newColor;
      this.onChange(newColor);
      return;
    }

    if (!Array.isArray(this.value)) {
      this.value = [{ condition: {}, color: newColor }];
    } else {
      // Update or add static mapping
      const staticIndex = this.value.findIndex(
        (m) => !Object.keys(m.condition || {}).length,
      );
      if (staticIndex >= 0) {
        this.value[staticIndex].color = newColor;
      } else {
        this.value.unshift({ condition: {}, color: newColor });
      }
    }

    this.onChange(this.value);
  }

  private updateValue(): void {
    this.onChange(this.value);
  }
}
<div class="conditional-color-config">
  <div class="color-row">
    <app-color-input
      [value]="staticColor"
      (valueChange)="onStaticColorChange($event)"
    ></app-color-input>

    <button
      mat-stroked-button
      type="button"
      (click)="addConditionalColorSection()"
      color="accent"
      matTooltip="Customize colors dynamically based on data values"
      i18n-matTooltip
    >
      <span i18n>Add Conditional Color</span>
    </button>
  </div>

  @if (isConditionalMode()) {
    <div class="conditional-sections flex-column gap-regular padding-regular">
      @for (section of conditionalColorSections; track $index) {
        <app-conditional-color-section
          [section]="section"
          [entityConstructor]="entityConstructor()"
          (colorChange)="updateConditionalSectionColor($index, $event)"
          (deleteSection)="deleteConditionalColorSection($index)"
          (conditionChange)="onConditionChange(section, $event)"
        ></app-conditional-color-section>
      }
    </div>
  }
</div>

./conditional-color-config.component.scss

.conditional-color-config {
  width: 100%;
}

.color-row {
  display: flex;
  gap: 12px;
  align-items: center;
  width: 100%;

  app-color-input {
    flex: 1;
  }

  button {
    flex-shrink: 0;
  }
}

.conditional-sections {
  background-color: var(--mdc-outlined-card-container-color, #fafafa);
  border-radius: 8px;
  width: 100%;
}

.add-section-container {
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: center;
  padding: 16px;
  border: 2px solid rgba(0, 0, 0, 0.12);
  border-radius: 8px;
  background-color: #fafafa;
  width: 100%;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""