diff --git a/src/app/app-modules/supervisor/reports/call-type-report.component.ts b/src/app/app-modules/supervisor/reports/call-type-report.component.ts
new file mode 100644
index 0000000..0509e85
--- /dev/null
+++ b/src/app/app-modules/supervisor/reports/call-type-report.component.ts
@@ -0,0 +1,280 @@
+/*
+ * AMRIT – Accessible Medical Records via Integrated Technologies
+ * Integrated EHR (Electronic Health Records) Solution
+ *
+ * Copyright (C) "Piramal Swasthya Management and Research Institute"
+ *
+ * This file is part of AMRIT.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see https://www.gnu.org/licenses/.
+ */
+
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ inject,
+ OnInit,
+ signal,
+} from '@angular/core';
+import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { ZardButtonComponent } from '@common-ui/ui/button';
+import { ZardInputDirective } from '@common-ui/ui/input';
+import { ZardSelectImports } from '@common-ui/ui/select';
+
+import {
+ clampReportEnd,
+ maxReportDay,
+ ReportsApiService,
+ reportDatesValidator,
+ saveBlob,
+} from './reports-api.service';
+import { dayBoundary, localDate } from '../allocation/allocation-api.service';
+import { BeneficiaryApiService } from '@/app-modules/core/services/beneficiary-api.service';
+import { LocationApiService } from '@/app-modules/core/services/location-api.service';
+import { NotificationService } from '@/app-modules/core/services/notification.service';
+import { CallType, CallTypeGroup, DistrictRow, RegistrationData } from '@/app-modules/core/models';
+import { SessionStore } from '@/app-modules/core/state/session.store';
+
+/**
+ * Call Type Report (supervisor case 9): call type/sub-type cascade + demographic filters →
+ * server-side XLSX blob. Same yesterday-cap/31-day window as the distribution reports; the
+ * old payload sent day-boundary strings with a `.999Z` end.
+ */
+@Component({
+ selector: 'app-call-type-report',
+ imports: [ReactiveFormsModule, ZardButtonComponent, ZardInputDirective, ...ZardSelectImports],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+ `,
+})
+export class CallTypeReportComponent implements OnInit {
+ private readonly fb = inject(FormBuilder);
+ private readonly api = inject(ReportsApiService);
+ private readonly beneficiaryApi = inject(BeneficiaryApiService);
+ private readonly locationApi = inject(LocationApiService);
+ private readonly notify = inject(NotificationService);
+ private readonly sessionStore = inject(SessionStore);
+
+ private readonly callTypeGroups = signal([]);
+ protected readonly callGroups = computed(() =>
+ this.callTypeGroups()
+ .map((g) => g.callGroupType ?? '')
+ .filter(Boolean),
+ );
+ protected readonly subTypes = signal([]);
+ protected readonly states = signal>([]);
+ protected readonly districts = signal([]);
+ protected readonly genders = signal>([]);
+ protected readonly languages = signal>([]);
+ protected readonly orientations = signal>([]);
+ protected readonly downloading = signal(false);
+
+ protected readonly maxDay = maxReportDay();
+ protected readonly maxEndDay = signal(this.maxDay);
+
+ protected readonly form = this.fb.group(
+ {
+ startDate: this.fb.control(null, Validators.required),
+ endDate: this.fb.control(null, Validators.required),
+ callType: this.fb.control(null),
+ callSubType: this.fb.control(null),
+ state: this.fb.control(null),
+ district: this.fb.control(null),
+ gender: this.fb.control(null),
+ language: this.fb.control(null),
+ sexuality: this.fb.control(null),
+ },
+ { validators: reportDatesValidator(this.maxDay, () => this.maxEndDay()) },
+ );
+
+ ngOnInit(): void {
+ const serviceId = this.sessionStore.currentServiceId();
+ if (serviceId == null) {
+ return;
+ }
+ this.api.getCallTypes(serviceId).subscribe({
+ next: (res) => this.callTypeGroups.set(res?.data ?? []),
+ error: (err: { errorMessage?: string }) =>
+ this.notify.alert(err?.errorMessage ?? 'Failed to load call types', 'error'),
+ });
+ this.beneficiaryApi.getRegistrationData(serviceId).subscribe({
+ next: (res) => {
+ if (res?.data) {
+ this.states.set(res.data.states ?? []);
+ this.genders.set(res.data.m_genders ?? []);
+ this.languages.set(res.data.m_language ?? []);
+ this.orientations.set(res.data.sexualOrientations ?? []);
+ }
+ },
+ error: (err: { errorMessage?: string }) =>
+ this.notify.alert(err?.errorMessage ?? 'Failed to load filters', 'error'),
+ });
+ }
+
+ protected onStartDateChange(): void {
+ const start = this.form.controls.startDate.value;
+ if (!start) {
+ return;
+ }
+ const end = clampReportEnd(start);
+ this.maxEndDay.set(end);
+ this.form.patchValue({ endDate: end });
+ }
+
+ // Handlers take the emitted value: z-select fires zValueChange BEFORE its CVA writes the
+ // form control, so reading the control here would see the previous selection.
+ protected onCallTypeChange(value: string | string[]): void {
+ // Old getCallSubType never cleared the chosen sub-type — a stale one stays in the payload.
+ this.subTypes.set(
+ this.callTypeGroups().find((g) => g.callGroupType === (value as string))?.callTypes ?? [],
+ );
+ }
+
+ protected onStateChange(value: string | string[]): void {
+ this.districts.set([]);
+ this.form.patchValue({ district: null });
+ const state = this.states().find((s) => s.stateName === (value as string));
+ if (state?.stateID == null) {
+ return;
+ }
+ this.locationApi.getDistricts(state.stateID).subscribe({
+ next: (res) => this.districts.set(res?.data ?? []),
+ error: (err: { errorMessage?: string }) =>
+ this.notify.alert(err?.errorMessage ?? 'Failed to load districts', 'error'),
+ });
+ }
+
+ protected download(): void {
+ const v = this.form.getRawValue();
+ if (!v.startDate || !v.endDate) {
+ return;
+ }
+ const body: Record = {
+ providerServiceMapID: this.sessionStore.currentServiceId(),
+ beneficiaryCallType: v.callType ?? undefined,
+ beneficiaryCallSubType: v.callSubType ?? undefined,
+ startTimestamp: dayBoundary(localDate(v.startDate), 'start'),
+ // Old call-type report ended the window at .999 (unlike the distribution reports).
+ endTimestamp: dayBoundary(localDate(v.endDate), 'end999'),
+ state: v.state ?? undefined,
+ district: v.district ?? undefined,
+ gender: v.gender ?? undefined,
+ beneficiaryPreferredLanguage: v.language ?? undefined,
+ beneficiarySexualOrientation: v.sexuality ?? undefined,
+ fileName: 'Call_Type_Report',
+ };
+ this.downloading.set(true);
+ this.api.downloadReport('crmReports/getAllReportsByDate', body).subscribe({
+ next: (blob) => {
+ this.downloading.set(false);
+ if (blob) {
+ saveBlob(blob, 'Call_Type_Report.xlsx');
+ this.notify.alert('Call type report downloaded', 'success');
+ } else {
+ this.notify.alert('No data found', 'info');
+ }
+ },
+ error: (err: { status?: number }) => {
+ this.downloading.set(false);
+ if (err?.status === 500) {
+ this.notify.alert('No data found', 'info');
+ } else {
+ this.notify.alert('Error while fetching report', 'error');
+ }
+ },
+ });
+ }
+}
diff --git a/src/app/app-modules/supervisor/reports/distribution-report.component.ts b/src/app/app-modules/supervisor/reports/distribution-report.component.ts
new file mode 100644
index 0000000..73b6639
--- /dev/null
+++ b/src/app/app-modules/supervisor/reports/distribution-report.component.ts
@@ -0,0 +1,306 @@
+/*
+ * AMRIT – Accessible Medical Records via Integrated Technologies
+ * Integrated EHR (Electronic Health Records) Solution
+ *
+ * Copyright (C) "Piramal Swasthya Management and Research Institute"
+ *
+ * This file is part of AMRIT.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see https://www.gnu.org/licenses/.
+ */
+
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ inject,
+ input,
+ OnInit,
+ signal,
+} from '@angular/core';
+import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { ZardButtonComponent } from '@common-ui/ui/button';
+import { ZardInputDirective } from '@common-ui/ui/input';
+import { ZardSelectImports } from '@common-ui/ui/select';
+
+import {
+ clampReportEnd,
+ maxReportDay,
+ ReportsApiService,
+ reportDatesValidator,
+ saveBlob,
+} from './reports-api.service';
+import { dayBoundary, localDate } from '../allocation/allocation-api.service';
+import { BeneficiaryApiService } from '@/app-modules/core/services/beneficiary-api.service';
+import { LocationApiService } from '@/app-modules/core/services/location-api.service';
+import { NotificationService } from '@/app-modules/core/services/notification.service';
+import { DistrictRow, RegistrationData } from '@/app-modules/core/models';
+import { SessionStore } from '@/app-modules/core/state/session.store';
+
+export type DistributionReportKind = 'age' | 'gender' | 'language' | 'sexualOrientation';
+
+interface DimensionOption {
+ display: string;
+ value: unknown;
+}
+
+interface ReportConfig {
+ title: string;
+ dimensionLabel: string;
+ path: string;
+ fileName: string;
+ options: (data: RegistrationData) => DimensionOption[];
+ dimension: (value: unknown) => Record;
+}
+
+/** Old hardcoded age buckets (min/max sent to the backend; 'All' → nulls). */
+const AGE_GROUPS: DimensionOption[] = [
+ { display: 'Below 15', value: { minAge: 0, maxAge: 15 } },
+ { display: '15 to 24', value: { minAge: 15, maxAge: 24 } },
+ { display: '25 to 39', value: { minAge: 25, maxAge: 39 } },
+ { display: '40 to 59', value: { minAge: 40, maxAge: 59 } },
+ { display: 'Above 59', value: { minAge: 59, maxAge: 150 } },
+ { display: 'All', value: 'All' },
+];
+
+const REPORTS: Record = {
+ age: {
+ title: 'Caller Age Report',
+ dimensionLabel: 'Age Group',
+ path: 'crmReports/getAllByAgeGroup',
+ fileName: 'Caller_Age_Group_Report',
+ options: () => AGE_GROUPS,
+ dimension: (value) => {
+ const group = value as { minAge?: number; maxAge?: number } | 'All';
+ const all = group === 'All';
+ return {
+ maxAge: all ? null : (group as { maxAge?: number }).maxAge,
+ minAge: all ? null : (group as { minAge?: number }).minAge,
+ callerAgeGroup: all
+ ? 'All'
+ : `${(group as { minAge?: number }).minAge} to ${(group as { maxAge?: number }).maxAge}`,
+ };
+ },
+ },
+ gender: {
+ title: 'Gender Distribution Report',
+ dimensionLabel: 'Gender',
+ path: 'crmReports/getAllByGender',
+ fileName: 'Gender_Distribution_Report',
+ options: (d) => [
+ ...(d.m_genders ?? []).map((g) => ({ display: g.genderName ?? '', value: g.genderName })),
+ { display: 'All', value: 'All' },
+ ],
+ dimension: (value) => ({ gender: value === 'All' ? null : value }),
+ },
+ language: {
+ title: 'Language Distribution Report',
+ dimensionLabel: 'Language',
+ path: 'crmReports/getCountsByPreferredLanguage',
+ fileName: 'Language_Distribution_Report',
+ options: (d) => [
+ ...(d.m_language ?? []).map((l) => ({ display: l.languageName ?? '', value: l.languageName })),
+ { display: 'All', value: 'All' },
+ ],
+ dimension: (value) => ({ beneficiaryPreferredLanguage: value === 'All' ? null : value }),
+ },
+ sexualOrientation: {
+ title: 'Sexual Orientation Report',
+ dimensionLabel: 'Sexual Orientation',
+ path: 'crmReports/getAllBySexualOrientation',
+ fileName: 'Sexual_Orientation_Report',
+ options: (d) => [
+ ...(d.sexualOrientations ?? []).map((s) => ({
+ display: s.sexualOrientation ?? '',
+ value: s.sexualOrientation,
+ })),
+ { display: 'All', value: 'All' },
+ ],
+ dimension: (value) => ({ beneficiarySexualOrientation: value === 'All' ? null : value }),
+ },
+};
+
+/**
+ * The four distribution reports (supervisor cases 15–18; old app had four near-identical
+ * components). Server-side XLSX blob; reports run up to YESTERDAY with a ≤31-day span.
+ */
+@Component({
+ selector: 'app-distribution-report',
+ imports: [ReactiveFormsModule, ZardButtonComponent, ZardInputDirective, ...ZardSelectImports],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+
{{ config().title }}
+
+
+ `,
+})
+export class DistributionReportComponent implements OnInit {
+ private readonly fb = inject(FormBuilder);
+ private readonly api = inject(ReportsApiService);
+ private readonly beneficiaryApi = inject(BeneficiaryApiService);
+ private readonly locationApi = inject(LocationApiService);
+ private readonly notify = inject(NotificationService);
+ private readonly sessionStore = inject(SessionStore);
+
+ readonly kind = input.required();
+
+ protected readonly config = computed(() => REPORTS[this.kind()]);
+ protected readonly states = signal>([]);
+ protected readonly districts = signal([]);
+ protected readonly options = signal([]);
+ protected readonly downloading = signal(false);
+
+ protected readonly maxDay = maxReportDay();
+ protected readonly maxEndDay = signal(this.maxDay);
+
+ protected readonly form = this.fb.group(
+ {
+ startDate: this.fb.control(null, Validators.required),
+ endDate: this.fb.control(null, Validators.required),
+ state: this.fb.control(null),
+ district: this.fb.control(null),
+ dimension: this.fb.control(null, Validators.required),
+ },
+ { validators: reportDatesValidator(this.maxDay, () => this.maxEndDay()) },
+ );
+
+ ngOnInit(): void {
+ // Old caller-age-report hardcoded its buckets in ngOnInit, independent of any API call.
+ if (this.kind() === 'age') {
+ this.options.set(AGE_GROUPS);
+ }
+ const serviceId = this.sessionStore.currentServiceId();
+ if (serviceId == null) {
+ return;
+ }
+ this.beneficiaryApi.getRegistrationData(serviceId).subscribe({
+ next: (res) => {
+ if (res?.data) {
+ this.states.set(res.data.states ?? []);
+ this.options.set(this.config().options(res.data));
+ }
+ },
+ error: (err: { errorMessage?: string }) =>
+ this.notify.alert(err?.errorMessage ?? 'Failed to load filters', 'error'),
+ });
+ }
+
+ protected onStartDateChange(): void {
+ const start = this.form.controls.startDate.value;
+ if (!start) {
+ return;
+ }
+ const end = clampReportEnd(start);
+ this.maxEndDay.set(end);
+ this.form.patchValue({ endDate: end });
+ }
+
+ // Takes the emitted value: z-select fires zValueChange BEFORE its CVA writes the form
+ // control, so reading the control here would see the previous selection.
+ protected onStateChange(value: string | string[]): void {
+ this.districts.set([]);
+ this.form.patchValue({ district: null });
+ const state = this.states().find((s) => s.stateName === (value as string));
+ if (state?.stateID == null) {
+ return;
+ }
+ this.locationApi.getDistricts(state.stateID).subscribe({
+ next: (res) => this.districts.set(res?.data ?? []),
+ error: (err: { errorMessage?: string }) =>
+ this.notify.alert(err?.errorMessage ?? 'Failed to load districts', 'error'),
+ });
+ }
+
+ protected download(): void {
+ const v = this.form.getRawValue();
+ if (!v.startDate || !v.endDate) {
+ return;
+ }
+ const option = this.options().find((o) => o.display === v.dimension);
+ const config = this.config();
+ const body: Record = {
+ startTimestamp: dayBoundary(localDate(v.startDate), 'start'),
+ endTimestamp: dayBoundary(localDate(v.endDate), 'end'),
+ providerServiceMapID: this.sessionStore.currentServiceId(),
+ ...config.dimension(option?.value),
+ state: v.state ?? undefined,
+ district: v.district || undefined,
+ fileName: config.fileName,
+ };
+ this.downloading.set(true);
+ this.api.downloadReport(config.path, body).subscribe({
+ next: (blob) => {
+ this.downloading.set(false);
+ if (blob) {
+ saveBlob(blob, `${config.fileName}.xlsx`);
+ this.notify.alert(`${config.title} downloaded`, 'success');
+ } else {
+ this.notify.alert('No data found', 'info');
+ }
+ },
+ error: (err: { status?: number }) => {
+ this.downloading.set(false);
+ if (err?.status === 500) {
+ this.notify.alert('No data found', 'info');
+ } else {
+ this.notify.alert('Error while fetching report', 'error');
+ }
+ },
+ });
+ }
+}
diff --git a/src/app/app-modules/supervisor/reports/reports-api.service.ts b/src/app/app-modules/supervisor/reports/reports-api.service.ts
new file mode 100644
index 0000000..f849498
--- /dev/null
+++ b/src/app/app-modules/supervisor/reports/reports-api.service.ts
@@ -0,0 +1,117 @@
+/*
+ * AMRIT – Accessible Medical Records via Integrated Technologies
+ * Integrated EHR (Electronic Health Records) Solution
+ *
+ * Copyright (C) "Piramal Swasthya Management and Research Institute"
+ *
+ * This file is part of AMRIT.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see https://www.gnu.org/licenses/.
+ */
+
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
+import { ValidatorFn } from '@angular/forms';
+import { Observable } from 'rxjs';
+
+import { localDate } from '../allocation/allocation-api.service';
+import { ApiResponse, CallTypeGroup } from '@/app-modules/core/models';
+import { ConfigService } from '@/app-modules/core/services/config.service';
+
+/** Trigger a browser download for a server-produced report blob (old `saveAs`). */
+export function saveBlob(blob: Blob, fileName: string): void {
+ const url = URL.createObjectURL(blob);
+ const anchor = document.createElement('a');
+ anchor.href = url;
+ anchor.download = fileName;
+ anchor.click();
+ // Revoking before the download starts can abort it; FileSaver kept the URL alive ~40s.
+ setTimeout(() => URL.revokeObjectURL(url), 40000);
+}
+
+/** Yesterday at local midnight — the reports' max selectable day (old `maxStartDate`). */
+export function yesterday(): Date {
+ const d = new Date();
+ d.setDate(d.getDate() - 1);
+ d.setHours(0, 0, 0, 0);
+ return d;
+}
+
+function toDayString(d: Date): string {
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
+}
+
+/** `yesterday()` as a yyyy-MM-dd string for native date-input `max`. */
+export function maxReportDay(): string {
+ return toDayString(yesterday());
+}
+
+/**
+ * Old `endDateChange`: the window ends at yesterday, capped to start+30 days when the start
+ * is 31+ days before yesterday. The result is both the auto-filled end date and its `max`.
+ */
+export function clampReportEnd(start: string): string {
+ const startDay = localDate(start);
+ const spanDays = Math.ceil((yesterday().getTime() - startDay.getTime()) / 86400000);
+ const end = new Date(startDay);
+ if (spanDays > 30) {
+ end.setDate(end.getDate() + 30);
+ } else {
+ end.setTime(yesterday().getTime());
+ }
+ return toDayString(end);
+}
+
+/**
+ * Native date inputs' min/max don't feed reactive-form validity (the old md2-datepicker's
+ * did, disabling Download on out-of-range dates) — so re-validate the window here.
+ */
+export function reportDatesValidator(maxDay: string, maxEnd: () => string): ValidatorFn {
+ return (group) => {
+ const start = group.get('startDate')?.value as string | null;
+ const end = group.get('endDate')?.value as string | null;
+ if (start && start > maxDay) {
+ return { startOutOfRange: true };
+ }
+ if (end && (end > maxEnd() || (start != null && end < start))) {
+ return { endOutOfRange: true };
+ }
+ return null;
+ };
+}
+
+/**
+ * `crmReports/*` XLSX downloads. The old ReportsService bypassed its interceptors with a
+ * construction-time token (stale-token bug); ours goes through the normal chain — the
+ * response interceptor passes non-envelope bodies (blobs) untouched.
+ */
+@Injectable({ providedIn: 'root' })
+export class ReportsApiService {
+ private readonly http = inject(HttpClient);
+ private readonly config = inject(ConfigService);
+
+ downloadReport(path: string, body: Record): Observable {
+ return this.http.post(`${this.config.commonBaseURL}${path}`, body, {
+ responseType: 'blob',
+ });
+ }
+
+ /** POST call/getCallTypesV1 with the report's plain body (no campaign flags). */
+ getCallTypes(providerServiceMapID: number): Observable> {
+ return this.http.post>(
+ `${this.config.commonBaseURL}call/getCallTypesV1`,
+ { providerServiceMapID },
+ );
+ }
+}
diff --git a/src/app/app-modules/supervisor/supervisor-shell.component.html b/src/app/app-modules/supervisor/supervisor-shell.component.html
index b81f1e1..0144a6e 100644
--- a/src/app/app-modules/supervisor/supervisor-shell.component.html
+++ b/src/app/app-modules/supervisor/supervisor-shell.component.html
@@ -80,9 +80,24 @@
@case ('14') {
}
+ @case ('15') {
+
+ }
+ @case ('16') {
+
+ }
+ @case ('17') {
+
+ }
+ @case ('18') {
+
+ }
@case ('7') {
}
+ @case ('9') {
+
+ }
@case ('12') {
}
diff --git a/src/app/app-modules/supervisor/supervisor-shell.component.ts b/src/app/app-modules/supervisor/supervisor-shell.component.ts
index c09ea7a..8da2131 100644
--- a/src/app/app-modules/supervisor/supervisor-shell.component.ts
+++ b/src/app/app-modules/supervisor/supervisor-shell.component.ts
@@ -30,6 +30,8 @@ import { menuImports } from '@common-ui/ui/menu';
import { CallAllocationComponent } from './allocation/call-allocation.component';
import { CallReallocationComponent } from './allocation/call-reallocation.component';
+import { CallTypeReportComponent } from './reports/call-type-report.component';
+import { DistributionReportComponent } from './reports/distribution-report.component';
import { TelephonyIframeComponent } from './telephony-iframe.component';
import { ConfigService } from '@/app-modules/core/services/config.service';
import { SessionStore } from '@/app-modules/core/state/session.store';
@@ -56,15 +58,15 @@ const PAGES: Record = {
'6': { label: 'Supervisor Notifications', arrives: 'a later phase (menu-less in the old app)' },
'7': { label: 'Telephony Reports' },
'8': { label: 'Supervisor Configurations', arrives: 'a later phase (menu-less in the old app)' },
- '9': { label: 'Call Type Report', arrives: 'Phase 7e' },
+ '9': { label: 'Call Type Report' },
'10': { label: 'Knowledge Management', arrives: 'Phase 7g' },
'12': { label: 'Outbound Call List' },
'13': { label: 'Outbound Call Re-Allocation' },
'14': { label: 'Campaign Status' },
- '15': { label: 'Caller Age Report', arrives: 'Phase 7e' },
- '16': { label: 'Sexual Orientation Report', arrives: 'Phase 7e' },
- '17': { label: 'Language Distribution Report', arrives: 'Phase 7e' },
- '18': { label: 'Gender Distribution Report', arrives: 'Phase 7e' },
+ '15': { label: 'Caller Age Report' },
+ '16': { label: 'Sexual Orientation Report' },
+ '17': { label: 'Language Distribution Report' },
+ '18': { label: 'Gender Distribution Report' },
'19': { label: 'Alerts and Notifications', arrives: 'Phase 7f' },
'20': { label: 'Location Messages', arrives: 'Phase 7f' },
'21': { label: 'Training Resource', arrives: 'Phase 7f' },
@@ -147,6 +149,8 @@ const CONFIGURATIONS_MENU: MenuEntry[] = [
TelephonyIframeComponent,
CallAllocationComponent,
CallReallocationComponent,
+ CallTypeReportComponent,
+ DistributionReportComponent,
...menuImports,
],
changeDetection: ChangeDetectionStrategy.OnPush,