src/app/features/reporting/reporting/select-report/select-report.component.ts

Metadata

Relationships

Depends on

Index

Properties
Methods
Inputs
Outputs
Accessors

Inputs

dateRangeOptions
Type : DateRangeFilterConfigOption[]

Optionally overwrite the default time periods shown to users for quick selection

exportableData
Type : any
loading
Type : boolean
Default value : false
reports
Type : ReportEntity[]

Outputs

calculateClick
Type : CalculateReportOptions
reportFiltersChange
Type : void
selectedReportChange
Type : ReportEntity

Methods

calculate
calculate()
Returns : void
onDateRangeChange
onDateRangeChange(event: { from: Date; to: Date })
Parameters :
Name Type Optional
event { from: Date; to: Date } No
Returns : void
openExportDialog
openExportDialog()
Returns : void
reportChange
reportChange()
Returns : void

Properties

dateRangeFilterConfig
Type : unknown
Default value : computed<DateFilter<any> | undefined>(() => { if (!this.isDateRangeReport()) return undefined; const options = this.dateRangeOptions()?.length > 0 ? this.dateRangeOptions() : defaultReportDateFilters; return new DateFilter<any>("reportPeriod", "Enter a date range", options); })
fromDate
Type : unknown
Default value : signal<Date | undefined>(undefined)
isDateRangeReport
Type : unknown
Default value : computed<boolean>(() => { const report = this.selectedReport(); if (!report) return false; // Primary signal: whether the report's queries actually use date placeholders. if (reportUsesDateRange(report)) return true; // Fallback for legacy configs that declared the date args explicitly: // v1 configs use neededArgs; v2/canonical use transformations. return ( !!report.neededArgs?.find((a) => a === "from" || a === "startDate") || !!report.neededArgs?.find((a) => a === "to" || a === "endDate") || !!report.transformations?.["startDate"] || !!report.transformations?.["endDate"] ); })

whether the currently selected report includes filter parameters for a "from" - "to" date range

selectedReport
Type : unknown
Default value : linkedSignal< ReportEntity[] | undefined, ReportEntity | undefined >({ source: this.reports, computation: (reports, previous) => reports?.length === 1 ? reports[0] : previous?.value, })
toDate
Type : unknown
Default value : signal<Date | undefined>(undefined)

Accessors

baseExportFileName
getbaseExportFileName()
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  inject,
  input,
  linkedSignal,
  output,
  signal,
} from "@angular/core";
import { MatDialog } from "@angular/material/dialog";
import { MatButtonModule } from "@angular/material/button";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatSelectModule } from "@angular/material/select";
import { FormsModule } from "@angular/forms";
import { MatDatepickerModule } from "@angular/material/datepicker";
import { Angulartics2Module } from "angulartics2";
import { ExportDialogComponent } from "#src/app/core/export/export-dialog/export-dialog.component";
import { FontAwesomeModule } from "@fortawesome/angular-fontawesome";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { MatTooltipModule } from "@angular/material/tooltip";
import { ReportEntity, reportUsesDateRange } from "../../report-config";
import { DateFilter } from "app/core/filter/filters/dateFilter";
import { DateRangeFilterComponent } from "app/core/basic-datatypes/date/date-range-filter/date-range-filter.component";
import { DateRangeFilterConfigOption } from "app/core/entity-list/EntityListConfig";
import { FaDynamicIconComponent } from "#src/app/core/common-components/fa-dynamic-icon/fa-dynamic-icon.component";

export const defaultReportDateFilters: DateRangeFilterConfigOption[] = [
  {
    startOffsets: [{ amount: 0, unit: "months" }],
    endOffsets: [{ amount: 0, unit: "months" }],
    label: $localize`:Filter label: Current month`,
  },
  {
    startOffsets: [{ amount: -1, unit: "months" }],
    endOffsets: [{ amount: -1, unit: "months" }],
    label: $localize`:Filter label: Last month`,
  },
  {
    startOffsets: [{ amount: 0, unit: "quarter" }],
    endOffsets: [{ amount: 0, unit: "quarter" }],
    label: $localize`:Filter label: Current quarter`,
  },
  {
    startOffsets: [{ amount: -1, unit: "quarters" }],
    endOffsets: [{ amount: -1, unit: "quarters" }],
    label: $localize`:Filter label: Last quarter`,
  },
  {
    startOffsets: [{ amount: 0, unit: "years" }],
    endOffsets: [{ amount: 0, unit: "years" }],
    label: $localize`:Filter label: Current year`,
  },
  {
    startOffsets: [{ amount: -1, unit: "years" }],
    endOffsets: [{ amount: -1, unit: "years" }],
    label: $localize`:Filter label: Last year`,
  },
];

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-select-report",
  templateUrl: "./select-report.component.html",
  styleUrls: ["./select-report.component.scss"],
  imports: [
    MatButtonModule,
    MatFormFieldModule,
    MatSelectModule,
    FormsModule,
    MatDatepickerModule,
    Angulartics2Module,
    FontAwesomeModule,
    MatProgressBarModule,
    MatTooltipModule,
    DateRangeFilterComponent,
    FaDynamicIconComponent,
  ],
})
export class SelectReportComponent {
  private readonly dialog = inject(MatDialog);

  reports = input<ReportEntity[]>();
  loading = input<boolean>(false);
  exportableData = input<any>();

  /** Optionally overwrite the default time periods shown to users for quick selection */
  dateRangeOptions = input<DateRangeFilterConfigOption[]>();

  calculateClick = output<CalculateReportOptions>();
  selectedReportChange = output<ReportEntity>();
  reportFiltersChange = output<void>();

  selectedReport = linkedSignal<
    ReportEntity[] | undefined,
    ReportEntity | undefined
  >({
    source: this.reports,
    computation: (reports, previous) =>
      reports?.length === 1 ? reports[0] : previous?.value,
  });
  fromDate = signal<Date | undefined>(undefined);
  toDate = signal<Date | undefined>(undefined);
  /** whether the currently selected report includes filter parameters for a "from" - "to" date range */
  isDateRangeReport = computed<boolean>(() => {
    const report = this.selectedReport();
    if (!report) return false;
    // Primary signal: whether the report's queries actually use date placeholders.
    if (reportUsesDateRange(report)) return true;
    // Fallback for legacy configs that declared the date args explicitly:
    // v1 configs use neededArgs; v2/canonical use transformations.
    return (
      !!report.neededArgs?.find((a) => a === "from" || a === "startDate") ||
      !!report.neededArgs?.find((a) => a === "to" || a === "endDate") ||
      !!report.transformations?.["startDate"] ||
      !!report.transformations?.["endDate"]
    );
  });

  dateRangeFilterConfig = computed<DateFilter<any> | undefined>(() => {
    if (!this.isDateRangeReport()) return undefined;
    const options =
      this.dateRangeOptions()?.length > 0
        ? this.dateRangeOptions()
        : defaultReportDateFilters;
    return new DateFilter<any>("reportPeriod", "Enter a date range", options);
  });

  calculate(): void {
    if (!this.isDateRangeReport()) {
      this.fromDate.set(undefined);
      this.toDate.set(undefined);
    }

    this.calculateClick.emit({
      report: this.selectedReport(),
      from: this.fromDate(),
      to: this.toDate(),
    });
  }

  reportChange() {
    this.selectedReportChange.emit(this.selectedReport());
  }

  onDateRangeChange(event: { from: Date; to: Date }) {
    this.fromDate.set(event.from);
    this.toDate.set(event.to);
    this.reportFiltersChange.emit();
  }

  openExportDialog() {
    this.dialog.open(ExportDialogComponent, {
      data: {
        allEntities: () => Promise.resolve(this.exportableData()),
        filename: this.baseExportFileName,
      },
    });
  }

  get baseExportFileName(): string {
    const reportName =
      this.selectedReport()
        ?.title?.replace(/[^\w\s-]/g, "")
        .replace(/\s+/g, " ")
        .trim() || "report";
    const datePart = this.getDatePart();
    return datePart ? `${reportName} ${datePart}` : reportName;
  }

  private getDatePart(): string {
    if (this.fromDate() && this.toDate()) {
      return `${this.formatDate(this.fromDate())}_${this.formatDate(this.toDate())}`;
    }
    return "";
  }

  private formatDate(date: Date): string {
    if (!date) return "";
    return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
  }
}

interface CalculateReportOptions {
  report: ReportEntity;
  from: Date;
  to: Date;
}
@if (!reports() || reports()?.length === 0) {
  <div>
    <p i18n>Reports have not been configured for you yet.</p>
    <a
      mat-stroked-button
      href="mailto:info@aam-digital.com?subject=Set up reports"
      i18n="Button if no reports are configured yet"
    >
      Ask for a setup call
    </a>
  </div>
}

@if (reports()?.length > 0) {
  <div class="work-panel">
    <div class="flex-row flex-wrap gap-regular">
      <mat-form-field class="flex-grow">
        <mat-label i18n>Select Report</mat-label>
        <mat-select
          [ngModel]="selectedReport()"
          [disabled]="loading()"
          (ngModelChange)="selectedReport.set($event); reportChange()"
        >
          @for (report of reports(); track report.getId()) {
            <mat-option [value]="report">
              {{ report.title }}
            </mat-option>
          }
        </mat-select>
        <mat-hint>{{ selectedReport()?.description }}</mat-hint>
      </mat-form-field>

      @if (isDateRangeReport()) {
        <app-date-range-filter
          [filterConfig]="dateRangeFilterConfig()"
          (dateRangeChange)="onDateRangeChange($event)"
        ></app-date-range-filter>
      }
    </div>
    <div>
      <button
        #calculateButton
        mat-stroked-button
        color="accent"
        class="primary-button"
        [disabled]="
          !selectedReport() ||
          (isDateRangeReport() && !fromDate() && !toDate()) ||
          loading()
        "
        (click)="calculate()"
        i18n="Calculate the results for a report"
        angulartics2On="click"
        angularticsCategory="Reporting"
        angularticsAction="calculate"
        [angularticsLabel]="selectedReport()?.title"
        matTooltip="Please select a report and a date range"
        i18n-matTooltip
        [matTooltipDisabled]="!calculateButton.disabled"
        style="pointer-events: all"
      >
        Calculate
      </button>
      <button
        mat-stroked-button
        color="accent"
        class="primary-button"
        [disabled]="!exportableData()"
        (click)="openExportDialog()"
        style="float: right"
        angulartics2On="click"
        angularticsCategory="Reporting"
        angularticsAction="export"
        [angularticsLabel]="selectedReport()?.title"
      >
        <app-fa-dynamic-icon
          icon="download"
          class="standard-icon-with-text"
          aria-hidden="true"
        ></app-fa-dynamic-icon>
        <ng-container i18n="Button to download data"> Download</ng-container>
      </button>
    </div>
    <div style="clear: both; padding-bottom: 5px"></div>
    @if (loading()) {
      <mat-progress-bar mode="indeterminate"></mat-progress-bar>
    }
  </div>
}

./select-report.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""