diff --git a/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.html b/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.html index f9ba93a77..cff5d52b3 100644 --- a/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.html +++ b/interfaces/IBF-dashboard/src/app/components/aggregates/aggregates.component.html @@ -6,7 +6,7 @@ style="background: var(--ion-color-ibf-secondary)" data-test="action-title" > - + + + + + + + +
@for (indicator of indicators; track indicator.name) { @@ -62,47 +75,15 @@ class="ion-text-right" data-test="aggregate-number" > - @switch (indicator.numberFormatAggregate) { - @case ('decimal2') { - - {{ - getAggregate( - indicator.name, - indicator.weightedAvg, - indicator.numberFormatAggregate - ) - | number: '.2-2' - | translate - }} - - } - @case ('decimal0') { - - {{ - getAggregate( - indicator.name, - indicator.weightedAvg, - indicator.numberFormatAggregate - ) - | number: '.0-0' - | translate - }} - - } - @case ('perc') { - - {{ - getAggregate( - indicator.name, - indicator.weightedAvg, - indicator.numberFormatAggregate - ) - | percent: '.0-0' - | translate - }} - - } - } + {{ + getAggregate( + indicator.name, + indicator.weightedAvg, + indicator.numberFormatAggregate + ) + | compact: indicator.numberFormatAggregate + | translate + }} {{ indicator.aggregateUnit }} } @else { diff --git a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html index bd680b7b4..54710870a 100644 --- a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html +++ b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.html @@ -189,14 +189,7 @@ " > - @switch (actionIndicatorNumberFormat) { - @case ('decimal0') { - {{ area.actionsValue | number: '.0-0' }} - } - @case ('perc') { - {{ area.actionsValue | percent: '.0-0' }} - } - } + {{ area.actionsValue | compact: actionIndicatorNumberFormat }} } diff --git a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.ts b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.ts index 255697490..2372c2e83 100644 --- a/interfaces/IBF-dashboard/src/app/components/chat/chat.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/chat/chat.component.ts @@ -29,7 +29,7 @@ import { AdminLevelService } from '../../services/admin-level.service'; import { AggregatesService } from '../../services/aggregates.service'; import { TimelineService } from '../../services/timeline.service'; import { AdminLevel, AdminLevelType } from '../../types/admin-level'; -import { Indicator } from '../../types/indicator-group'; +import { Indicator, NumberFormat } from '../../types/indicator-group'; import { LeadTimeTriggerKey, LeadTimeUnit } from '../../types/lead-time'; import { TriggeredArea } from '../../types/triggered-area'; import { ActionResultPopoverComponent } from '../action-result-popover/action-result-popover.component'; @@ -72,7 +72,7 @@ export class ChatComponent implements OnInit, OnDestroy { public disasterTypeLabel: string; public disasterTypeName: string; public actionIndicatorLabel: string; - public actionIndicatorNumberFormat: string; + public actionIndicatorNumberFormat: NumberFormat; public forecastInfo: string[]; public country: Country; public disasterType: DisasterType; diff --git a/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.html b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.html new file mode 100644 index 000000000..41ded8fa0 --- /dev/null +++ b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.html @@ -0,0 +1,13 @@ + + + + + {{ 'disclaimer-approximate-component.message' | translate }} + + + diff --git a/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.scss b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.scss new file mode 100644 index 000000000..5069ca1ac --- /dev/null +++ b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.scss @@ -0,0 +1,4 @@ +ion-popover { + --offset-x: 8px; + --offset-y: -2px; +} diff --git a/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.spec.ts b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.spec.ts new file mode 100644 index 000000000..458792d9b --- /dev/null +++ b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { DisclaimerApproximateComponent } from './disclaimer-approximate.component'; + +describe('DisclaimerApproximateComponent', () => { + let component: DisclaimerApproximateComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DisclaimerApproximateComponent], + imports: [IonicModule, TranslateModule.forRoot()], + }).compileComponents(); + + fixture = TestBed.createComponent(DisclaimerApproximateComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.ts b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.ts new file mode 100644 index 000000000..ff8fc4eae --- /dev/null +++ b/interfaces/IBF-dashboard/src/app/components/disclaimer-approximate/disclaimer-approximate.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-disclaimer-approximate', + templateUrl: './disclaimer-approximate.component.html', + styleUrls: ['./disclaimer-approximate.component.scss'], +}) +export class DisclaimerApproximateComponent {} diff --git a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html index 4bf023db2..23c8efb77 100644 --- a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html +++ b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.html @@ -147,7 +147,7 @@ '(' + (actionIndicatorLabel | titlecase) + ' ' + - (event.actionsValueSum | number: '.0-0') + + (event.actionsValueSum | compact) + ')' }} } @else { @@ -220,14 +220,9 @@ } @if (area.actionsValue && area.actionsValue > 0) { {{ ' - ' }} - @switch (actionIndicatorNumberFormat) { - @case ('decimal0') { - {{ area.actionsValue | number: '.0-0' }} - } - @case ('perc') { - {{ area.actionsValue | percent: '.0-0' }} - } - } + {{ + area.actionsValue | compact: actionIndicatorNumberFormat + }} }

@@ -281,14 +276,9 @@ ({{ area.nameParent }}) } - - @switch (actionIndicatorNumberFormat) { - @case ('decimal0') { - {{ area.actionsValue | number: '.0-0' }} - } - @case ('perc') { - {{ area.actionsValue | percent: '.0-0' }} - } - } + {{ + area.actionsValue | compact: actionIndicatorNumberFormat + }}

} diff --git a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.ts b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.ts index 596e5d3fd..65cd065b5 100644 --- a/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.ts +++ b/interfaces/IBF-dashboard/src/app/components/event-speech-bubble/event-speech-bubble.component.ts @@ -16,6 +16,7 @@ import { TimelineService } from '../../services/timeline.service'; import { DisasterTypeKey } from '../../types/disaster-type-key'; import { LeadTime, LeadTimeTriggerKey } from '../../types/lead-time'; import { TriggeredArea } from '../../types/triggered-area'; +import { NumberFormat } from '../../types/indicator-group'; @Component({ selector: 'app-event-speech-bubble', @@ -46,7 +47,7 @@ export class EventSpeechBubbleComponent implements AfterViewChecked, OnDestroy { @Input() public actionIndicatorLabel: string; @Input() - public actionIndicatorNumberFormat: string; + public actionIndicatorNumberFormat: NumberFormat; public typhoonLandfallText: string; public displayName: string; diff --git a/interfaces/IBF-dashboard/src/app/config.backup.ts b/interfaces/IBF-dashboard/src/app/config.backup.ts deleted file mode 100644 index 033b4963b..000000000 --- a/interfaces/IBF-dashboard/src/app/config.backup.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const DEBOUNCE_TIME_LOADER = 500; -const DEBUG = {}; - -console.log = (string, val) => { - if (string === 'mapComponent newLayer') { - if (val && val.name === 'flood_extent') { - console.info(string, val); // 23 times - } - } - // check for duplicates - // AggregatesComponent leadTime - // mapComponent adminLevel - // mapComponent leadTime - - // console.info('b', string, DEBUG[string]); - DEBUG[string] = DEBUG[string] ? DEBUG[string] + 1 : 1; - // console.info('a', string, DEBUG[string]); - // console.log(DEBUG); -}; - -console.error = () => { - console.info(DEBUG); -}; diff --git a/interfaces/IBF-dashboard/src/app/pages/dashboard/dashboard.page.html b/interfaces/IBF-dashboard/src/app/pages/dashboard/dashboard.page.html index b6543d4fb..f46bcfa29 100644 --- a/interfaces/IBF-dashboard/src/app/pages/dashboard/dashboard.page.html +++ b/interfaces/IBF-dashboard/src/app/pages/dashboard/dashboard.page.html @@ -139,7 +139,7 @@ > @@ -149,7 +149,7 @@ - + diff --git a/interfaces/IBF-dashboard/src/app/pipes/compact.pipe.spec.ts b/interfaces/IBF-dashboard/src/app/pipes/compact.pipe.spec.ts new file mode 100644 index 000000000..df0446814 --- /dev/null +++ b/interfaces/IBF-dashboard/src/app/pipes/compact.pipe.spec.ts @@ -0,0 +1,8 @@ +import { CompactPipe } from './compact.pipe'; + +describe('CompactPipe', () => { + it('create an instance', () => { + const pipe = new CompactPipe('en-US'); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/interfaces/IBF-dashboard/src/app/pipes/compact.pipe.ts b/interfaces/IBF-dashboard/src/app/pipes/compact.pipe.ts new file mode 100644 index 000000000..a9361be3f --- /dev/null +++ b/interfaces/IBF-dashboard/src/app/pipes/compact.pipe.ts @@ -0,0 +1,23 @@ +import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core'; +import { NumberFormat } from '../types/indicator-group'; + +@Pipe({ + standalone: true, + name: 'compact', +}) +export class CompactPipe implements PipeTransform { + constructor(@Inject(LOCALE_ID) private locale: string) {} + + transform(value: number, format: NumberFormat = NumberFormat.decimal0) { + const style = format === NumberFormat.perc ? 'percent' : 'decimal'; + const min = format === NumberFormat.perc ? 0.1 : 10; + + value = value > 0 ? Math.max(min, value) : 0; + + return new Intl.NumberFormat(this.locale, { + maximumSignificantDigits: 1, + style, + notation: 'compact', + }).format(value); + } +} diff --git a/interfaces/IBF-dashboard/src/app/shared.module.ts b/interfaces/IBF-dashboard/src/app/shared.module.ts index 594fae130..3f3913495 100644 --- a/interfaces/IBF-dashboard/src/app/shared.module.ts +++ b/interfaces/IBF-dashboard/src/app/shared.module.ts @@ -48,6 +48,9 @@ import { TooltipPopoverComponent } from './components/tooltip-popover/tooltip-po import { TooltipComponent } from './components/tooltip/tooltip.component'; import { UserStateComponent } from './components/user-state/user-state.component'; import { BackendMockScenarioComponent } from './mocks/backend-mock-scenario-component/backend-mock-scenario.component'; +import { CompactPipe } from './pipes/compact.pipe'; +import { DisclaimerApproximateComponent } from './components/disclaimer-approximate/disclaimer-approximate.component'; + @NgModule({ imports: [ AnalyticsModule, @@ -57,6 +60,7 @@ import { BackendMockScenarioComponent } from './mocks/backend-mock-scenario-comp LeafletMarkerClusterModule, LeafletModule, TranslateModule, + CompactPipe, ], declarations: [ AboutBtnComponent, @@ -101,6 +105,7 @@ import { BackendMockScenarioComponent } from './mocks/backend-mock-scenario-comp DynamicPointPopupComponent, TyphoonTrackpointPopupContentComponent, GlofasStationPopupContentComponent, + DisclaimerApproximateComponent, ], exports: [ AboutBtnComponent, @@ -146,6 +151,7 @@ import { BackendMockScenarioComponent } from './mocks/backend-mock-scenario-comp DynamicPointPopupComponent, TyphoonTrackpointPopupContentComponent, GlofasStationPopupContentComponent, + DisclaimerApproximateComponent, ], }) export class SharedModule {} diff --git a/interfaces/IBF-dashboard/src/assets/i18n/en.json b/interfaces/IBF-dashboard/src/assets/i18n/en.json index d5515f686..f6e035ca1 100644 --- a/interfaces/IBF-dashboard/src/assets/i18n/en.json +++ b/interfaces/IBF-dashboard/src/assets/i18n/en.json @@ -71,6 +71,9 @@ "plural-suffix": "(s)", "not-applicable": "N/A" }, + "disclaimer-approximate-component": { + "message": "All numbers are approximate and meant to be used as guidance." + }, "areas-of-focus-summary-component": { "title": "Actions Summary", "aof-subtitle": "Sector", diff --git a/interfaces/IBF-dashboard/src/global.scss b/interfaces/IBF-dashboard/src/global.scss index e32a5bbe1..d4809e286 100644 --- a/interfaces/IBF-dashboard/src/global.scss +++ b/interfaces/IBF-dashboard/src/global.scss @@ -310,3 +310,11 @@ app-tooltip { } } } + +.margin-top-auto { + margin-top: auto; +} + +.font-size-12 { + font-size: 12px; +} diff --git a/interfaces/IBF-dashboard/src/shared/utils.ts b/interfaces/IBF-dashboard/src/shared/utils.ts index 383424405..0151c5d89 100644 --- a/interfaces/IBF-dashboard/src/shared/utils.ts +++ b/interfaces/IBF-dashboard/src/shared/utils.ts @@ -1,18 +1,7 @@ // sort array ascending -const asc = (arr) => arr.sort((a, b) => a - b); +const asc = (arr: number[]) => arr.sort((a: number, b: number) => a - b); -// const sum = (arr) => arr.reduce((a, b) => a + b, 0); - -// const mean = (arr) => sum(arr) / arr.length; - -// sample standard deviation -// const std = (arr) => { -// const mu = mean(arr); -// const diffArr = arr.map((a) => (a - mu) ** 2); -// return Math.sqrt(sum(diffArr) / (arr.length - 1)); -// }; - -export const quantile = (arr, q) => { +export const quantile = (arr: number[], q: number) => { const sorted = asc(arr); const pos = (sorted.length - 1) * q; const base = Math.floor(pos); diff --git a/interfaces/IBF-dashboard/src/theme/ibf-colors/design-system-colors/_fiveten-neutral-700.scss b/interfaces/IBF-dashboard/src/theme/ibf-colors/design-system-colors/_fiveten-neutral-700.scss index ea880b644..ff91f39ec 100644 --- a/interfaces/IBF-dashboard/src/theme/ibf-colors/design-system-colors/_fiveten-neutral-700.scss +++ b/interfaces/IBF-dashboard/src/theme/ibf-colors/design-system-colors/_fiveten-neutral-700.scss @@ -1,10 +1,10 @@ :root { - --ion-color-fiveten-neutral-700: #4f5763; - --ion-color-fiveten-neutral-700-rgb: 79, 87, 99; + --ion-color-fiveten-neutral-700: #49515b; + --ion-color-fiveten-neutral-700-rgb: 73, 81, 91; --ion-color-fiveten-neutral-700-contrast: #ffffff; --ion-color-fiveten-neutral-700-contrast-rgb: 255, 255, 255; - --ion-color-fiveten-neutral-700-shade: #464d57; - --ion-color-fiveten-neutral-700-tint: #616873; + --ion-color-fiveten-neutral-700-shade: #404750; + --ion-color-fiveten-neutral-700-tint: #5b626b; } .ion-color-fiveten-neutral-700 { diff --git a/services/API-service/src/api/metadata/dto/add-indicators.dto.ts b/services/API-service/src/api/metadata/dto/add-indicators.dto.ts index e0029e2d2..4d00cf3e5 100644 --- a/services/API-service/src/api/metadata/dto/add-indicators.dto.ts +++ b/services/API-service/src/api/metadata/dto/add-indicators.dto.ts @@ -8,6 +8,8 @@ import { IsString, } from 'class-validator'; +import { NumberFormat } from '../../../shared/enums/number-format.enum'; + export class IndicatorDto { @ApiProperty({ example: { @@ -53,9 +55,9 @@ export class IndicatorDto { }) public colorBreaks: JSON; - @ApiProperty({ example: 'decimal0' }) + @ApiProperty({ example: NumberFormat.decimal0 }) @IsString() - public numberFormatMap: string; + public numberFormatMap: NumberFormat; @ApiProperty({ example: 'decimal0' }) @IsIn(['decimal0', 'decimal2', 'perc']) diff --git a/services/API-service/src/api/metadata/indicator-metadata.entity.ts b/services/API-service/src/api/metadata/indicator-metadata.entity.ts index ce8ab7512..bf60318e6 100644 --- a/services/API-service/src/api/metadata/indicator-metadata.entity.ts +++ b/services/API-service/src/api/metadata/indicator-metadata.entity.ts @@ -2,6 +2,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { NumberFormat } from '../../shared/enums/number-format.enum'; + @Entity('indicator-metadata') export class IndicatorMetadataEntity { @ApiProperty({ example: '6b9b7669-4839-4fdb-9645-9070a27bda86' }) @@ -49,9 +51,9 @@ export class IndicatorMetadataEntity { @Column('json', { nullable: true }) public colorBreaks: JSON; - @ApiProperty({ example: 'decimal0' }) + @ApiProperty({ example: NumberFormat.decimal0 }) @Column() - public numberFormatMap: string; + public numberFormatMap: NumberFormat; @ApiProperty({ example: 'decimal0' }) @Column() diff --git a/services/API-service/src/api/notification/email/email-template.service.ts b/services/API-service/src/api/notification/email/email-template.service.ts index 405a189ab..b53547902 100644 --- a/services/API-service/src/api/notification/email/email-template.service.ts +++ b/services/API-service/src/api/notification/email/email-template.service.ts @@ -9,6 +9,7 @@ import { EventSummaryCountry, TriggeredArea, } from '../../../shared/data.model'; +import { HelperService } from '../../../shared/helper.service'; import { LeadTime } from '../../admin-area-dynamic-data/enum/lead-time.enum'; import { CountryTimeZoneMapping } from '../../country/country-time-zone-mapping'; import { CountryEntity } from '../../country/country.entity'; @@ -26,6 +27,8 @@ const emailLogoFolder = `${emailFolder}/logos`; @Injectable() export class EmailTemplateService { + public constructor(private readonly helperService: HelperService) {} + public async createHtmlForTriggerEmail( emailContent: ContentEventEmail, date: Date, @@ -303,7 +306,9 @@ export class EmailTemplateService { .map((area) => { const areaTemplate = this.readHtmlFile('table-row.html'); const areaData = { - affectedOfIndicator: area.actionsValue, + affectedOfIndicator: this.helperService.toCompactNumber( + area.actionsValue, + ), adminBoundary: area.displayName ? area.displayName : area.name, higherAdminBoundary: area.nameParent, }; @@ -414,7 +419,9 @@ export class EmailTemplateService { : 'body-total-affected-unavailable.html'; const htmlTemplate = this.readHtmlFile(fileName); return ejs.render(htmlTemplate, { - totalAffectedOfIndicator: event.totalAffectedOfIndicator, + totalAffectedOfIndicator: this.helperService.toCompactNumber( + event.totalAffectedOfIndicator, + ), indicatorUnit: indicatorUnit, }); } diff --git a/services/API-service/src/api/notification/email/html/body-total-affected-available.html b/services/API-service/src/api/notification/email/html/body-total-affected-available.html index e5f37e53d..3086099c3 100644 --- a/services/API-service/src/api/notification/email/html/body-total-affected-available.html +++ b/services/API-service/src/api/notification/email/html/body-total-affected-available.html @@ -1,4 +1,4 @@ - <%= totalAffectedOfIndicator %> <%= indicatorUnit %> + <%= totalAffectedOfIndicator %> <%= indicatorUnit %> (approx.) diff --git a/services/API-service/src/api/notification/email/html/header.html b/services/API-service/src/api/notification/email/html/header.html index 2e93f08d9..d8a9585b9 100644 --- a/services/API-service/src/api/notification/email/html/header.html +++ b/services/API-service/src/api/notification/email/html/header.html @@ -2,7 +2,7 @@ -
+

<%= nrOfEvents %> <%= disasterTypeLabel %> alerts

diff --git a/services/API-service/src/api/notification/email/html/styles.ejs b/services/API-service/src/api/notification/email/html/styles.ejs index 2c2d5b125..cf94f066b 100644 --- a/services/API-service/src/api/notification/email/html/styles.ejs +++ b/services/API-service/src/api/notification/email/html/styles.ejs @@ -25,6 +25,9 @@ .body-text-white { color: white; } + .body-text-grey { + color: #49515b; + } .body-text-12px { font-size: 12px; line-height: 14.4px; @@ -146,11 +149,4 @@ cellspacing: 0; cellpadding: 0; } - .centered-text { - text-align: center; - } - - .white-text { - color: white; - } diff --git a/services/API-service/src/api/notification/email/html/trigger-notification.html b/services/API-service/src/api/notification/email/html/trigger-notification.html index ae414f340..c1a052490 100644 --- a/services/API-service/src/api/notification/email/html/trigger-notification.html +++ b/services/API-service/src/api/notification/email/html/trigger-notification.html @@ -93,6 +93,13 @@ <%- mapImagePart %>

+
+ All numbers are approximate and meant to be used + as guidance. +
+
<%- tablesStacked %> <%- footer %> diff --git a/services/API-service/src/api/notification/helpers/format-action-unit-value.helper.ts b/services/API-service/src/api/notification/helpers/format-action-unit-value.helper.ts deleted file mode 100644 index ff365fd72..000000000 --- a/services/API-service/src/api/notification/helpers/format-action-unit-value.helper.ts +++ /dev/null @@ -1,16 +0,0 @@ -export function formatActionUnitValue( - value: number, - numberFormat: string, -): string { - if (value === null) { - return null; - } else if (numberFormat === 'perc') { - return Math.round(value * 100).toLocaleString() + '%'; - } else if (numberFormat === 'decimal2') { - return (Math.round(value * 100) / 100).toLocaleString(); - } else if (numberFormat === 'decimal0') { - return Math.round(value).toLocaleString(); - } else { - return Math.round(value).toLocaleString(); - } -} diff --git a/services/API-service/src/api/notification/notification.module.ts b/services/API-service/src/api/notification/notification.module.ts index 833c61d7b..3aacc55aa 100644 --- a/services/API-service/src/api/notification/notification.module.ts +++ b/services/API-service/src/api/notification/notification.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { HelperService } from '../../shared/helper.service'; import { IndicatorMetadataEntity } from '../metadata/indicator-metadata.entity'; import { TyphoonTrackModule } from '../typhoon-track/typhoon-track.module'; import { UserModule } from '../user/user.module'; @@ -25,6 +26,11 @@ import { WhatsappModule } from './whatsapp/whatsapp.module'; TyphoonTrackModule, ], controllers: [NotificationController], - providers: [NotificationService, EmailService, EmailTemplateService], + providers: [ + NotificationService, + EmailService, + EmailTemplateService, + HelperService, + ], }) export class NotificationModule {} diff --git a/services/API-service/src/api/notification/whatsapp/whatsapp.module.ts b/services/API-service/src/api/notification/whatsapp/whatsapp.module.ts index 653537e2e..271bc41ac 100644 --- a/services/API-service/src/api/notification/whatsapp/whatsapp.module.ts +++ b/services/API-service/src/api/notification/whatsapp/whatsapp.module.ts @@ -7,6 +7,7 @@ import { import { TypeOrmModule } from '@nestjs/typeorm'; import { API_PATHS } from '../../../config'; +import { HelperService } from '../../../shared/helper.service'; import { CountryEntity } from '../../country/country.entity'; import { EventMapImageEntity } from '../../event/event-map-image.entity'; import { EventModule } from '../../event/event.module'; @@ -30,7 +31,7 @@ import { WhatsappService } from './whatsapp.service'; EventModule, NotificationContentModule, ], - providers: [WhatsappService], + providers: [WhatsappService, HelperService], controllers: [WhatsappController], exports: [WhatsappService], }) diff --git a/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts b/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts index 681ff1699..30f3e26d5 100644 --- a/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts +++ b/services/API-service/src/api/notification/whatsapp/whatsapp.service.ts @@ -5,12 +5,12 @@ import { IsNull, Not, Repository } from 'typeorm'; import { EXTERNAL_API } from '../../../config'; import { EventSummaryCountry } from '../../../shared/data.model'; +import { HelperService } from '../../../shared/helper.service'; import { CountryEntity } from '../../country/country.entity'; import { DisasterType } from '../../disaster/disaster-type.enum'; import { EventMapImageEntity } from '../../event/event-map-image.entity'; import { EventService } from '../../event/event.service'; import { UserEntity } from '../../user/user.entity'; -import { formatActionUnitValue } from '../helpers/format-action-unit-value.helper'; import { LookupService } from '../lookup/lookup.service'; import { NotificationContentService } from '../notification-content/notification-content.service'; import { twilioClient } from './twilio.client'; @@ -35,6 +35,7 @@ export class WhatsappService { private readonly eventService: EventService, private readonly lookupService: LookupService, private readonly notificationContentService: NotificationContentService, + private readonly helperService: HelperService, ) {} public async sendTestWhatsapp( @@ -377,7 +378,7 @@ export class WhatsappService { for (const area of triggeredAreas) { const row = `- *${area.name}${ area.nameParent ? ' (' + area.nameParent + ')' : '' - } - ${formatActionUnitValue( + } - ${this.helperService.toCompactNumber( area.actionsValue, indicatorMetadata.numberFormatMap, )}*\n`; diff --git a/services/API-service/src/shared/enums/number-format.enum.ts b/services/API-service/src/shared/enums/number-format.enum.ts new file mode 100644 index 000000000..ae50fe826 --- /dev/null +++ b/services/API-service/src/shared/enums/number-format.enum.ts @@ -0,0 +1,5 @@ +export enum NumberFormat { + decimal0 = 'decimal0', + decimal2 = 'decimal2', + perc = 'perc', +} diff --git a/services/API-service/src/shared/helper.service.ts b/services/API-service/src/shared/helper.service.ts index a10e2c406..12f955365 100644 --- a/services/API-service/src/shared/helper.service.ts +++ b/services/API-service/src/shared/helper.service.ts @@ -11,6 +11,7 @@ import { import { DisasterType } from '../api/disaster/disaster-type.enum'; import { DateDto } from '../api/event/dto/date.dto'; import { TriggerPerLeadTime } from '../api/event/trigger-per-lead-time.entity'; +import { NumberFormat } from './enums/number-format.enum'; import { GeoJson, GeoJsonFeature } from './geo.model'; @Injectable() @@ -119,4 +120,21 @@ export class HelperService { }; } } + + public toCompactNumber( + value: number, + format: NumberFormat = NumberFormat.decimal0, + locale = 'en-GB', + ) { + const style = format === NumberFormat.perc ? 'percent' : 'decimal'; + const min = format === NumberFormat.perc ? 0.1 : 10; + + value = value > 0 ? Math.max(min, value) : 0; + + return new Intl.NumberFormat(locale, { + maximumSignificantDigits: 1, + style, + notation: 'compact', + }).format(value); + } }