diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 221aa6f9..5b917328 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -10,7 +10,6 @@ import { OnDestroy, } from "@angular/core"; import { environment } from "../environments/environment"; -import { ConfigsService } from "./configs/configs.service"; import { LookupsService } from "./lookups/lookups.service"; import { Router, NavigationEnd } from "@angular/router"; import { MenuItem, Message, MessageService, PrimeNGConfig } from "primeng/api"; @@ -28,7 +27,7 @@ declare let jQuery: any; selector: "app-root", templateUrl: "./app.component.html", styleUrls: ["./app.component.scss"], - providers: [LookupsService, ConfigsService, MessageService], + providers: [LookupsService, MessageService], }) export class AppComponent implements AfterViewInit, OnInit, OnDestroy { @ViewChild("layoutContainer", { static: false }) diff --git a/src/app/configs/configs.service.spec.ts b/src/app/configs/configs.service.spec.ts deleted file mode 100644 index fcef4cb5..00000000 --- a/src/app/configs/configs.service.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TestBed, inject } from "@angular/core/testing"; - -import { ConfigsService } from "./configs.service"; -import { HttpClientTestingModule } from "@angular/common/http/testing"; - -describe("ConfigsService", () => { - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ConfigsService], - imports: [HttpClientTestingModule], - }); - }); - - it("should be created", inject( - [ConfigsService], - (service: ConfigsService) => { - expect(service).toBeTruthy(); - } - )); -}); diff --git a/src/app/configs/configs.service.ts b/src/app/configs/configs.service.ts deleted file mode 100644 index 17f899e1..00000000 --- a/src/app/configs/configs.service.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Observable, of, throwError } from "rxjs"; - -import { first, mergeMap, map, pluck, catchError, tap } from "rxjs/operators"; -import { Injectable } from "@angular/core"; -import { environment } from "../../environments/environment"; -import { Config, CCategory } from "../shared/models/config"; -import { ConfigsService as ConfigsClient } from "machete-client"; -import { MS_NON_EDITABLE_CONFIGS_LOWER_CASE } from "./machete-settings/shared/machete-settings-constants"; -import { MessagesService } from "../shared/components/messages/messages.service"; -@Injectable() -export class ConfigsService { - uriBase = environment.dataUrl + "/api/configs"; - configs = new Array(); - configsAge = 0; - constructor( - private client: ConfigsClient, - private appMessages: MessagesService - ) { - client.configuration.withCredentials = false; - } - - isStale(): boolean { - if (this.configsAge > Date.now() - 36000) { - return false; - } - return true; - } - isNotStale(): boolean { - return !this.isStale(); - } - - getAllConfigs(): Observable { - if (this.isNotStale()) { - return of(this.configs); - } - - // withCredentials: true is normally necessary, but configs are enabled for anonymous - return this.client.apiConfigsGet().pipe( - pluck("data"), - map((data) => data as Config[]), - tap((data) => (this.configs = data)), - tap(() => (this.configsAge = Date.now())) - ); - } - - getConfigs(category: CCategory): Observable { - return this.getAllConfigs().pipe( - map((res) => res.filter((l) => l.category === category)) - ); - } - - getConfig(key: string): Observable { - return this.getAllConfigs().pipe( - mergeMap((a) => a.filter((ll) => ll.key === key)), - map((a) => ({ ...a })), // return a new object - first() - ); - } - - update(config: Config): Observable { - if (MS_NON_EDITABLE_CONFIGS_LOWER_CASE.includes(config.key.toLowerCase())) { - return this.getConfig(config.key).pipe( - tap(() => { - this.appMessages.showErrors({ - Error: "Action not allowed", - }); - }) - ); - } - - return this.client.apiConfigsIdPut(config.id, config).pipe( - catchError((error) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const errorAsText: string = error["statusText"] as string; - this.appMessages.showErrors({ - Error: `${errorAsText}: Contact Machete support.`, - }); - console.log(error); - return throwError(error); - }), - pluck("data"), - map((data) => data as Config), - tap(() => { - this.appMessages.showSuccess({ - label: "Success", - message: "Record Saved", - }); - }) - ); - } -} diff --git a/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.html b/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.html index 43d85b35..dd5176d2 100644 --- a/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.html +++ b/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.html @@ -43,6 +43,19 @@
+ + + + +
+
{ declarations: [MacheteSettingsEditComponent], providers: [ { - provide: ConfigsService, - useClass: ConfigsServiceSpy, + provide: AppSettingsStoreServiceSpy, + useClass: AppSettingsStoreService, }, { provide: ConfirmationService, diff --git a/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.ts b/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.ts index 6fe77b7c..2029c785 100644 --- a/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.ts +++ b/src/app/configs/machete-settings/machete-settings-edit/machete-settings-edit.component.ts @@ -5,7 +5,7 @@ import { ConfirmationService } from "primeng/api"; import { merge, Observable, Subject } from "rxjs"; import { pluck, switchMap, takeWhile, tap } from "rxjs/operators"; import { Config } from "src/app/shared/models/config"; -import { ConfigsService } from "../../configs.service"; +import { AppSettingsStoreService } from "../../../shared/services/app-settings-store.service"; @Component({ selector: "app-machete-settings-edit", @@ -25,14 +25,14 @@ export class MacheteSettingsEditComponent implements OnInit, OnDestroy { pluck("id"), switchMap((id: string) => { this.routeRecordId = id; - return this.configsService.getConfig(this.routeRecordId); + return this.appSettingsStore.getConfig(this.routeRecordId); }) ); constructor( private activatedRoute: ActivatedRoute, private router: Router, - private configsService: ConfigsService, + private appSettingsStore: AppSettingsStoreService, private primeConfirmService: ConfirmationService ) {} @@ -52,7 +52,7 @@ export class MacheteSettingsEditComponent implements OnInit, OnDestroy { } public save(record: Config): void { - this.configsService + this.appSettingsStore .update(record) .pipe( takeWhile(() => this.isAlive), diff --git a/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.spec.ts b/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.spec.ts index 23c519d3..d9872875 100644 --- a/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.spec.ts +++ b/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; -import { ConfigsServiceSpy, RouterSpy } from "src/app/shared/testing"; -import { ConfigsService } from "../../configs.service"; +import { AppSettingsStoreServiceSpy, RouterSpy } from "src/app/shared/testing"; +import { AppSettingsStoreService } from "../../../shared/services/app-settings-store.service"; import { MacheteSettingsListComponent } from "./machete-settings-list.component"; @@ -13,7 +13,10 @@ describe("MacheteSettingsListComponent", () => { await TestBed.configureTestingModule({ declarations: [MacheteSettingsListComponent], providers: [ - { provide: ConfigsService, useClass: ConfigsServiceSpy }, + { + provide: AppSettingsStoreService, + useClass: AppSettingsStoreServiceSpy, + }, { provide: Router, useClass: RouterSpy }, ], }).compileComponents(); diff --git a/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.ts b/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.ts index 0fae8f45..82e7d340 100644 --- a/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.ts +++ b/src/app/configs/machete-settings/machete-settings-list/machete-settings-list.component.ts @@ -3,7 +3,7 @@ import { Router } from "@angular/router"; import { Observable } from "rxjs"; import { map } from "rxjs/operators"; import { Config } from "src/app/shared/models/config"; -import { ConfigsService } from "../../configs.service"; +import { AppSettingsStoreService } from "../../../shared/services/app-settings-store.service"; import { MS_NON_EDITABLE_CONFIGS } from "../shared/machete-settings-constants"; @Component({ @@ -20,7 +20,10 @@ export class MacheteSettingsListComponent implements OnInit { "id", ]; - constructor(private service: ConfigsService, private router: Router) {} + constructor( + private appSetttingsServ: AppSettingsStoreService, + private router: Router + ) {} async onRowSelect(selectedRecord: Config): Promise { await this.router.navigate([ @@ -29,7 +32,7 @@ export class MacheteSettingsListComponent implements OnInit { } ngOnInit(): void { - this.configs$ = this.service.getAllConfigs().pipe( + this.configs$ = this.appSetttingsServ.all$.pipe( map((configs: Config[]) => configs.filter((c) => c.publicConfig)), map((configs: Config[]) => configs.filter((c) => !MS_NON_EDITABLE_CONFIGS.includes(c.key)) diff --git a/src/app/my-work-orders/order-complete/order-complete.component.spec.ts b/src/app/my-work-orders/order-complete/order-complete.component.spec.ts index 5ef65a59..57f510ff 100644 --- a/src/app/my-work-orders/order-complete/order-complete.component.spec.ts +++ b/src/app/my-work-orders/order-complete/order-complete.component.spec.ts @@ -8,7 +8,7 @@ import { TableModule } from "primeng/table"; import { MyWorkOrdersServiceSpy, RouterSpy, - ConfigsServiceSpy, + AppSettingsStoreServiceSpy, } from "../../shared/testing"; import { TransportProvidersServiceSpy, @@ -17,9 +17,9 @@ import { import * as paypal from "paypal-checkout"; import { MyWorkOrdersService } from "../my-work-orders.service"; import { ActivatedRoute, Router } from "@angular/router"; -import { ConfigsService } from "../../configs/configs.service"; import { MessageService } from "primeng/api"; import { TransportProvidersService } from "../../online-orders/transport-providers.service"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; describe("OrderCompleteComponent", () => { let component: OrderCompleteComponent; @@ -42,7 +42,10 @@ describe("OrderCompleteComponent", () => { provide: MyWorkOrdersService, useClass: MyWorkOrdersServiceSpy, }, - { provide: ConfigsService, useClass: ConfigsServiceSpy }, + { + provide: AppSettingsStoreService, + useClass: AppSettingsStoreServiceSpy, + }, { provide: MessageService, useClass: MessageServiceSpy }, { provide: ActivatedRoute, diff --git a/src/app/my-work-orders/order-complete/order-complete.component.ts b/src/app/my-work-orders/order-complete/order-complete.component.ts index 9276013a..05d6decf 100644 --- a/src/app/my-work-orders/order-complete/order-complete.component.ts +++ b/src/app/my-work-orders/order-complete/order-complete.component.ts @@ -8,8 +8,8 @@ import { WorkOrder } from "../../shared/models/work-order"; import * as paypal from "paypal-checkout"; import { ActivatedRoute, Router } from "@angular/router"; import { MyWorkOrdersService } from "../my-work-orders.service"; -import { ConfigsService } from "../../configs/configs.service"; import { TransportProvidersService } from "../../online-orders/transport-providers.service"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; @Component({ selector: "app-order-complete", @@ -80,7 +80,7 @@ export class OrderCompleteComponent implements OnInit, AfterViewChecked { private transportProviderService: TransportProvidersService, private route: ActivatedRoute, private router: Router, - private configsService: ConfigsService + private appSettingsStore: AppSettingsStoreService ) { console.log(".ctor"); } @@ -91,8 +91,8 @@ export class OrderCompleteComponent implements OnInit, AfterViewChecked { observableCombineLatest([ this.transportProviderService.getTransportProviders(), this.ordersService.getOrder(orderId), - this.configsService.getConfig("PayPalClientID"), - this.configsService.getConfig("PayPalEnvironment"), + this.appSettingsStore.getConfig("PayPalClientID"), + this.appSettingsStore.getConfig("PayPalEnvironment"), ]).subscribe( ([l, o, id, env]) => { console.log("ngOnInit:combineLatest received:", l, o, id, env); diff --git a/src/app/online-orders/guards/banner.guard.ts b/src/app/online-orders/guards/banner.guard.ts index 98c211c3..b93939ba 100644 --- a/src/app/online-orders/guards/banner.guard.ts +++ b/src/app/online-orders/guards/banner.guard.ts @@ -2,18 +2,21 @@ import { Injectable } from "@angular/core"; import { CanActivate, Router } from "@angular/router"; import { combineLatest, Observable } from "rxjs"; import { map } from "rxjs/operators"; -import { ConfigsService } from "../..//configs/configs.service"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; @Injectable() export class BannerGuard implements CanActivate { - constructor(private configsService: ConfigsService, private router: Router) { + constructor( + private appSettingsStore: AppSettingsStoreService, + private router: Router + ) { console.log(".ctor"); } canActivate(): Observable { return combineLatest([ - this.configsService.getConfig("DisableOnlineOrders"), - this.configsService.getConfig("DisableOnlineOrdersBanner"), + this.appSettingsStore.getConfig("DisableOnlineOrders"), + this.appSettingsStore.getConfig("DisableOnlineOrdersBanner"), ]).pipe( map( ([toggle, banner]) => { diff --git a/src/app/online-orders/intro-confirm/intro-confirm.component.ts b/src/app/online-orders/intro-confirm/intro-confirm.component.ts index 6fbd886e..32580101 100644 --- a/src/app/online-orders/intro-confirm/intro-confirm.component.ts +++ b/src/app/online-orders/intro-confirm/intro-confirm.component.ts @@ -11,7 +11,6 @@ import { Confirm } from "../shared/models/confirm"; export class IntroConfirmComponent implements OnInit { confirmChoices = new Array(); confirmStatus = false; - // TODO: Refactor as a service that polls from API constructor( private onlineService: OnlineOrdersService, diff --git a/src/app/online-orders/introduction/introduction.component.html b/src/app/online-orders/introduction/introduction.component.html index 1bbce49f..17324e40 100644 --- a/src/app/online-orders/introduction/introduction.component.html +++ b/src/app/online-orders/introduction/introduction.component.html @@ -3,26 +3,9 @@ About Casa Latina -

- Casa Latina connects Latino immigrant workers with individuals and - businesses looking for temporary labor. Our workers are skilled and - dependable. -

-

- From landscaping to dry walling to catering and housecleaning, if you can - dream the project our workers can do it! For more information about our - program please read these Frequently Asked Questions If you are ready to - hire a worker, please fill out the following form. -

- -

- Casa Latina is taking all necessary precautions to keep employers and - workers safe, therefore some jobs might not be available at this time. -

-

- If you still have questions about hiring a worker, please call us at - 206.956.0779 x3. -

+ +

+
@@ -46,15 +29,15 @@
- Online Hiring Temporarily Unavailable diff --git a/src/app/online-orders/introduction/introduction.component.spec.ts b/src/app/online-orders/introduction/introduction.component.spec.ts index 0bc06688..b563d406 100644 --- a/src/app/online-orders/introduction/introduction.component.spec.ts +++ b/src/app/online-orders/introduction/introduction.component.spec.ts @@ -6,14 +6,14 @@ import { OnlineOrdersServiceSpy, WorkOrderServiceSpy, WorkAssignmentsServiceSpy, - ConfigsServiceSpy, + AppSettingsStoreServiceSpy, } from "../../shared/testing"; import { Router } from "@angular/router"; import { OnlineOrdersService } from "../online-orders.service"; import { WorkOrderService } from "../work-order/work-order.service"; import { WorkAssignmentsService } from "../work-assignments/work-assignments.service"; -import { ConfigsService } from "../../configs/configs.service"; import { CardModule } from "primeng/card"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; describe("IntroductionComponent", () => { let component: IntroductionComponent; @@ -38,7 +38,10 @@ describe("IntroductionComponent", () => { provide: WorkAssignmentsService, useClass: WorkAssignmentsServiceSpy, }, - { provide: ConfigsService, useClass: ConfigsServiceSpy }, + { + provide: AppSettingsStoreService, + useClass: AppSettingsStoreServiceSpy, + }, ], }, }) diff --git a/src/app/online-orders/introduction/introduction.component.ts b/src/app/online-orders/introduction/introduction.component.ts index a8bf7d90..747bfed6 100644 --- a/src/app/online-orders/introduction/introduction.component.ts +++ b/src/app/online-orders/introduction/introduction.component.ts @@ -3,7 +3,7 @@ import { Router } from "@angular/router"; import { OnlineOrdersService } from "../online-orders.service"; import { WorkOrderService } from "../work-order/work-order.service"; import { WorkAssignmentsService } from "../work-assignments/work-assignments.service"; -import { ConfigsService } from "../../configs/configs.service"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; @Component({ selector: "app-introduction", templateUrl: "./introduction.component.html", @@ -13,16 +13,17 @@ export class IntroductionComponent implements OnInit { macheteOutage = false; outageMessage: string; outageVisible = false; + stepOneIntro$ = this.appSettingsStore.getConfig("OnlineOrdersIntroMessage"); constructor( private router: Router, private onlineService: OnlineOrdersService, private orderService: WorkOrderService, private assignmentService: WorkAssignmentsService, - private cfgService: ConfigsService + private appSettingsStore: AppSettingsStoreService ) {} ngOnInit(): void { - this.cfgService.getAllConfigs().subscribe( + this.appSettingsStore.all$.subscribe( (data) => { this.macheteOutage = data.find((config) => config.key === "DisableOnlineOrders").value === diff --git a/src/app/online-orders/online-orders.module.ts b/src/app/online-orders/online-orders.module.ts index 78541012..21d38332 100644 --- a/src/app/online-orders/online-orders.module.ts +++ b/src/app/online-orders/online-orders.module.ts @@ -45,7 +45,6 @@ import { WorkOrdersModule } from "../shared/components/work-orders/work-orders.m import { ProfileGuard } from "./guards/profile.guard"; import { LayoutModule } from "@angular/cdk/layout"; import { SkillsComponent } from "./work-assignments/skills/skills.component"; -import { ConfigsService } from "../configs/configs.service"; import { BannerGuard } from "./guards/banner.guard"; @NgModule({ @@ -88,10 +87,8 @@ import { BannerGuard } from "./guards/banner.guard"; SkillsComponent, ], providers: [ - OnlineOrdersService, ScheduleRulesService, TransportRulesService, - ConfigsService, ProfileGuard, BannerGuard, MessageService, //PrimeNG Service diff --git a/src/app/online-orders/online-orders.service.spec.ts b/src/app/online-orders/online-orders.service.spec.ts index dbf94f00..4f57a62a 100644 --- a/src/app/online-orders/online-orders.service.spec.ts +++ b/src/app/online-orders/online-orders.service.spec.ts @@ -15,7 +15,9 @@ import { EmployersServiceSpy, AuthServiceSpy, WorkAssignmentsServiceSpy, + AppSettingsStoreServiceSpy, } from "../shared/testing"; +import { AppSettingsStoreService } from "../shared/services/app-settings-store.service"; describe("OnlineOrdersService", () => { beforeEach(() => { @@ -25,6 +27,10 @@ describe("OnlineOrdersService", () => { { provide: WorkOrderService, useClass: WorkOrderServiceSpy }, { provide: EmployersService, useClass: EmployersServiceSpy }, { provide: AuthService, useClass: AuthServiceSpy }, + { + provide: AppSettingsStoreService, + useClass: AppSettingsStoreServiceSpy, + }, { provide: WorkAssignmentsService, useClass: WorkAssignmentsServiceSpy, diff --git a/src/app/online-orders/online-orders.service.ts b/src/app/online-orders/online-orders.service.ts index b998b08e..18bc7ef2 100644 --- a/src/app/online-orders/online-orders.service.ts +++ b/src/app/online-orders/online-orders.service.ts @@ -1,14 +1,19 @@ -import { map } from "rxjs/operators"; -import { Injectable } from "@angular/core"; -import { Observable, BehaviorSubject } from "rxjs"; -import { WorkOrder } from "../shared/models/work-order"; -import { HttpHeaders } from "@angular/common/http"; - +import { map, tap } from "rxjs/operators"; +import { Injectable, OnDestroy } from "@angular/core"; +import { Observable, BehaviorSubject, Subscription } from "rxjs"; import { Confirm } from "./shared/models/confirm"; -import { loadConfirms } from "./shared/rules/load-confirms"; -import { OnlineOrdersService as OnlineOrdersClient } from "machete-client"; -@Injectable() -export class OnlineOrdersService { +import { AppSettingsStoreService } from "../shared/services/app-settings-store.service"; +import { + ConfigVM as Config, + WorkOrderVM as WorkOrder, + WorkOrdersService as OrdersClient, +} from "machete-client"; +import { OnlineOrderTerm } from "../configs/machete-settings/machete-settings-edit/online-order-term"; + +@Injectable({ + providedIn: "root", +}) +export class OnlineOrdersService implements OnDestroy { storageKey = "machete.online-orders-service"; initialConfirmKey = this.storageKey + ".initialconfirm"; workOrderConfirmKey = this.storageKey + ".workorderconfirm"; @@ -17,12 +22,32 @@ export class OnlineOrdersService { private initialConfirmSource: BehaviorSubject; private workOrderConfirmSource = new BehaviorSubject(false); private workAssignmentsConfirmSource = new BehaviorSubject(false); + private confirmChoices = new Array(); + private termsSubscription: Subscription; + private terms$: Observable = this.appSettingsStore + .getConfig("OnlineOrdersTerms") + .pipe( + map((config: Config) => JSON.parse(config.value) as OnlineOrderTerm[]), + map((terms: OnlineOrderTerm[]) => + terms.map( + (x) => + new Confirm({ name: x.name, description: x.text, confirmed: false }) + ) + ), + tap((terms: Confirm[]) => (this.confirmChoices = terms)) + ); - constructor(private client: OnlineOrdersClient) { + constructor( + private ordersClient: OrdersClient, + private appSettingsStore: AppSettingsStoreService + ) { console.log(".ctor: OnlineOrdersService"); - // this loads static data from a file. will replace later. - - this.loadConfirmState(); + this.termsSubscription = this.terms$.subscribe(() => + this.loadConfirmState() + ); + } + ngOnDestroy(): void { + this.termsSubscription.unsubscribe(); } getInitialConfirmedStream(): Observable { @@ -49,7 +74,7 @@ export class OnlineOrdersService { ); } else { this.initialConfirmSource = new BehaviorSubject( - loadConfirms() + this.confirmChoices ); } @@ -64,7 +89,7 @@ export class OnlineOrdersService { clearState(): void { console.log("OnlineOrdersService.clearState-----"); - this.setInitialConfirm(loadConfirms()); + this.setInitialConfirm(this.confirmChoices); this.setWorkorderConfirm(false); this.setWorkAssignmentsConfirm(false); } @@ -91,8 +116,8 @@ export class OnlineOrdersService { } createOrder(order: WorkOrder): Observable { - return this.client - .apiOnlineordersPost(order) + return this.ordersClient + .apiWorkordersPost(order) .pipe(map((data) => data["data"] as WorkOrder)); } } diff --git a/src/app/online-orders/order-confirm/order-confirm.component.html b/src/app/online-orders/order-confirm/order-confirm.component.html index 5a2bdc4a..b11186c9 100644 --- a/src/app/online-orders/order-confirm/order-confirm.component.html +++ b/src/app/online-orders/order-confirm/order-confirm.component.html @@ -1,18 +1,13 @@
- - -
- You have chosen a transport method that has fees associated with it. You will need to - pay the fees before the workers will be dispatched. You can pay using our PayPal form, - or call 206.956.0779 x3 to make arrangements. When you finalize this order below, you - will be taken to the PayPal transaction page. -
+ +
diff --git a/src/app/online-orders/order-confirm/order-confirm.component.spec.ts b/src/app/online-orders/order-confirm/order-confirm.component.spec.ts index daab4334..d8453e63 100644 --- a/src/app/online-orders/order-confirm/order-confirm.component.spec.ts +++ b/src/app/online-orders/order-confirm/order-confirm.component.spec.ts @@ -9,6 +9,7 @@ import { OnlineOrdersServiceSpy, WorkAssignmentsServiceSpy, RouterSpy, + AppSettingsStoreServiceSpy, } from "../../shared/testing"; import { TableModule } from "primeng/table"; import { WorkAssignmentsService } from "../work-assignments/work-assignments.service"; @@ -16,6 +17,7 @@ import { Router } from "@angular/router"; import { FullOrderViewComponent } from "../../shared/components/work-orders/full-order-view/full-order-view.component"; import { MessageService } from "primeng/api"; import { TransportProvidersService } from "../transport-providers.service"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; describe("OrderConfirmComponent", () => { let component: OrderConfirmComponent; @@ -45,6 +47,10 @@ describe("OrderConfirmComponent", () => { useClass: WorkAssignmentsServiceSpy, }, { provide: Router, useClass: RouterSpy }, + { + provide: AppSettingsStoreService, + useClass: AppSettingsStoreServiceSpy, + }, ], }, }) diff --git a/src/app/online-orders/order-confirm/order-confirm.component.ts b/src/app/online-orders/order-confirm/order-confirm.component.ts index 482799da..31f955e7 100644 --- a/src/app/online-orders/order-confirm/order-confirm.component.ts +++ b/src/app/online-orders/order-confirm/order-confirm.component.ts @@ -8,6 +8,9 @@ import { Router } from "@angular/router"; import { TransportProvidersService } from "../transport-providers.service"; import { MessageService } from "primeng/api"; import { HttpErrorResponse } from "@angular/common/http"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; +import { Config } from "../../shared/models/config"; +import { map } from "rxjs/operators"; @Component({ selector: "app-order-confirm", @@ -20,6 +23,9 @@ export class OrderConfirmComponent implements OnInit { workerCount: number; transportCost: number; laborCost: number; + transportFeeNotice$: Observable = this.appSettingsStore + .getConfig("OrderConfirmTransportFeesNotice") + .pipe(map((config: Config) => config.value)); constructor( private ordersService: WorkOrderService, @@ -27,12 +33,13 @@ export class OrderConfirmComponent implements OnInit { private transportProviderService: TransportProvidersService, private assignmentService: WorkAssignmentsService, private messageService: MessageService, - private router: Router + private router: Router, + private appSettingsStore: AppSettingsStoreService ) {} ngOnInit(): void { const l$ = this.transportProviderService.getTransportProviders(); - const o$ = this.ordersService.getStream() as Observable; + const o$ = this.ordersService.getStream(); const wa$ = this.assignmentService.getStream(); observableCombineLatest([l$, o$, wa$]).subscribe( diff --git a/src/app/online-orders/work-order/work-order.component.html b/src/app/online-orders/work-order/work-order.component.html index e8243548..60ee74a4 100644 --- a/src/app/online-orders/work-order/work-order.component.html +++ b/src/app/online-orders/work-order/work-order.component.html @@ -146,18 +146,20 @@
- -
- - - - - - {{formErrors.requireVaccinatedWorkers}} - -
- + + +
+ + + + + + {{formErrors.requireVaccinatedWorkers}} + +
+
+
@@ -212,17 +214,18 @@
- - - - + + + + +
diff --git a/src/app/online-orders/work-order/work-order.component.spec.ts b/src/app/online-orders/work-order/work-order.component.spec.ts index 1dde9a3b..8cb8c383 100644 --- a/src/app/online-orders/work-order/work-order.component.spec.ts +++ b/src/app/online-orders/work-order/work-order.component.spec.ts @@ -11,15 +11,14 @@ import { ToggleButtonModule } from "primeng/togglebutton"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { OnlineOrdersService } from "../online-orders.service"; -import { ConfigsService } from "../../configs/configs.service"; import { TransportRulesServiceSpy, TransportProvidersServiceSpy, ScheduleRulesServiceSpy, + AppSettingsStoreServiceSpy, } from "../../shared/testing"; import { WorkOrderServiceSpy, - ConfigsServiceSpy, OnlineOrdersServiceSpy, RouterSpy, } from "../../shared/testing"; @@ -28,6 +27,7 @@ import { Router } from "@angular/router"; import { ScheduleRulesService } from "../schedule-rules.service"; import { TransportRulesService } from "../transport-rules.service"; import { TransportProvidersService } from "../transport-providers.service"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; describe("WorkOrderComponent", () => { let component: WorkOrderComponent; @@ -55,7 +55,10 @@ describe("WorkOrderComponent", () => { provide: OnlineOrdersService, useClass: OnlineOrdersServiceSpy, }, - { provide: ConfigsService, useClass: ConfigsServiceSpy }, + { + provide: AppSettingsStoreService, + useClass: AppSettingsStoreServiceSpy, + }, { provide: Router, useClass: RouterSpy }, { provide: ScheduleRulesService, diff --git a/src/app/online-orders/work-order/work-order.component.ts b/src/app/online-orders/work-order/work-order.component.ts index d80f0663..38a9c556 100644 --- a/src/app/online-orders/work-order/work-order.component.ts +++ b/src/app/online-orders/work-order/work-order.component.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { combineLatest as observableCombineLatest } from "rxjs"; +import { combineLatest as observableCombineLatest, Observable } from "rxjs"; import { Component, OnInit } from "@angular/core"; import { FormGroup, FormBuilder } from "@angular/forms"; import { WorkOrder } from "../../shared/models/work-order"; @@ -31,6 +31,8 @@ import { transportAvailabilityValidator } from "../shared/validators/transport-a import { DateTime } from "luxon"; import { BreakpointObserver, Breakpoints } from "@angular/cdk/layout"; import { vaccineReqFlagResolver } from "../../shared/helpers"; +import { AppSettingsStoreService } from "../../shared/services/app-settings-store.service"; +import { map, pluck } from "rxjs/operators"; @Component({ selector: "app-work-order", @@ -80,11 +82,21 @@ export class WorkOrderComponent implements OnInit { new YesNoSelectItem("no", false), new YesNoSelectItem("yes", true), ]; + onlineOrdersTransportDetailsLink$: Observable = this.appSettingsStore + .getConfig("OnlineOrdersTransportDetailsLink") + .pipe(pluck("value")); + disableWorkersVaccineRequirement$: Observable = this.appSettingsStore + .getConfig("DisableWorkersVaccineRequirement") + .pipe( + pluck("value"), + map((flag: string) => flag === "TRUE") + ); constructor( private transportProviderService: TransportProvidersService, private orderService: WorkOrderService, private onlineService: OnlineOrdersService, + private appSettingsStore: AppSettingsStoreService, private schedulingRulesService: ScheduleRulesService, private transportRulesService: TransportRulesService, private router: Router, diff --git a/src/app/shared/services/app-settings-store.service.spec.ts b/src/app/shared/services/app-settings-store.service.spec.ts new file mode 100644 index 00000000..bd9e1f5a --- /dev/null +++ b/src/app/shared/services/app-settings-store.service.spec.ts @@ -0,0 +1,57 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { HttpClientTestingModule } from "@angular/common/http/testing"; +import { TestBed } from "@angular/core/testing"; +import { MessagesService } from "../components/messages/messages.service"; +import { MessagesServiceSpy } from "../testing"; + +import { AppSettingsStoreService } from "./app-settings-store.service"; +import { ConfigsService } from "machete-client"; +import { takeWhile } from "rxjs/operators"; + +describe("AppSettingsStoreService", () => { + let service: AppSettingsStoreService; + let configsClientSpy: any; + let killSubscriptions: boolean; + + beforeEach(() => { + configsClientSpy = jasmine.createSpyObj("ConfigsService", [ + "apiConfigsGet", + "apiConfigsIdPut", + ]); + killSubscriptions = false; + TestBed.configureTestingModule({ + providers: [ + AppSettingsStoreService, + ConfigsService, + { provide: MessagesService, useClass: MessagesServiceSpy }, + ], + imports: [HttpClientTestingModule], + }); + service = TestBed.inject(AppSettingsStoreService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("when STALE, SHOULD call Api", () => { + configsClientSpy.apiConfigsGet.calls.reset(); + service.refreshCache(); + service.all$.subscribe(() => { + expect(configsClientSpy.apiConfigsGet).toHaveBeenCalledTimes(1); + }); + }); + + it("when NOT stale, should not callApi", () => { + service.all$ + .pipe(takeWhile(() => !killSubscriptions)) + .subscribe(() => configsClientSpy.apiConfigsGet.calls.reset()); //initial http request + service.setCacheTTL(5000); + service.all$.pipe(takeWhile(() => !killSubscriptions)).subscribe((res) => { + console.log("______RES_____", res); + expect(configsClientSpy.apiConfigsGet).toHaveBeenCalledTimes(0); + }); // chache + }); +}); diff --git a/src/app/shared/services/app-settings-store.service.ts b/src/app/shared/services/app-settings-store.service.ts new file mode 100644 index 00000000..50eebcdb --- /dev/null +++ b/src/app/shared/services/app-settings-store.service.ts @@ -0,0 +1,155 @@ +import { HttpErrorResponse } from "@angular/common/http"; +import { Injectable, Optional, SkipSelf } from "@angular/core"; +import { BehaviorSubject, iif, Observable, throwError } from "rxjs"; +import { catchError, map, pluck, share, tap } from "rxjs/operators"; +import { environment } from "../../../environments/environment"; +import { ConfigsService as ConfigsClient } from "machete-client"; +import { MS_NON_EDITABLE_CONFIGS_LOWER_CASE } from "../../configs/machete-settings/shared/machete-settings-constants"; +import { MessagesService } from "../components/messages/messages.service"; +import { CCategory, Config } from "../models/config"; + +/** + * Singleton store service. Caches data based on a TTL. + * Each config object represents an app setting + */ +@Injectable({ + providedIn: "root", +}) +export class AppSettingsStoreService { + private _configsAge = 0; + private _CACHE_TIME_TO_LIVE = 300000; // 5 minutes + private readonly _uriBase = environment.dataUrl + "/api/configs"; + private _appSettingsSubject = new BehaviorSubject([]); + + /** + * All Configs as Observable. Cached based on a TTL + */ + readonly all$ = iif( + () => this.isStale(), + this.getAllConfigs(), + this._appSettingsSubject.asObservable() + ); + + constructor( + private _configsClient: ConfigsClient, + private _appMessages: MessagesService, + @Optional() @SkipSelf() parentModule?: AppSettingsStoreService + ) { + // enforce app singleton pattern + if (parentModule) { + throw new Error( + `Machete dev error: ${AppSettingsStoreService.name} is already loaded. Additional imports not needed` + ); + } + } + + private isStale = (): boolean => + this._configsAge < Date.now() - this._CACHE_TIME_TO_LIVE; + + private getAllConfigs(): Observable { + return this._configsClient.apiConfigsGet().pipe( + pluck("data"), + map((data: Config[]) => data), + tap(() => (this._configsAge = Date.now())), + tap((configs: Config[]) => this._appSettingsSubject.next(configs)), + share() + ); + } + + private httpUpdate(config: Config): Observable { + return this._configsClient.apiConfigsIdPut(config.id, config).pipe( + catchError((error: HttpErrorResponse) => { + const { statusText } = error; + this._appMessages.showErrors({ + Error: `${statusText}: Contact Machete support.`, + }); + console.log(error); + return throwError(error); + }), + pluck("data"), // if no error + map((data: Config) => data) + ); + } + + private validateUserEditable = (configKey: string): boolean => + !MS_NON_EDITABLE_CONFIGS_LOWER_CASE.includes(configKey.toLowerCase()); + + private getCacheWithNewVal(newConfig: Config, cache: Config[]) { + const configIndex = cache.findIndex((x) => x.id == newConfig.id); + cache.splice(configIndex, 1, newConfig); // side effect: changes the cache array + return cache; + } + + /** + * filters the cached values on `CCategory` + * @param category a `CCategory` type + * @returns the filtered values from cache as Observable, + * if the cache's TTL is reached, + * the new values + */ + getConfigsWith(category: CCategory): Observable { + return this.all$.pipe( + map((all) => all.filter((l) => l.category === category)) + ); + } + + /** + * @param key the config key property to filter + * @returns a single `Config` record from cache as Observable, + * if the cache's TTL is reached, sourced from new values + */ + getConfig(key: string): Observable { + return this.all$.pipe( + map((a) => a.filter((ll) => ll.key === key)), + map(([first]) => first), + map((a) => ({ ...a })) + ); + } + + /** + * makes an http PUT request and the cache is updated to reflect + * @param config the `Config` to update + * @returns the updated record returned from the http PUT request as Observable + */ + update(config: Config): Observable { + if (!this.validateUserEditable(config.key)) { + return this.getConfig(config.key).pipe( + tap(() => { + this._appMessages.showErrors({ + Error: "Action not allowed", + }); + }) + ); + } + + return this.httpUpdate(config).pipe( + tap(() => { + this._appMessages.showSuccess({ + label: "Success", + message: "Record Saved", + }); + }), + // replace cache with new val + tap((config) => + this._appSettingsSubject.next( + this.getCacheWithNewVal(config, this._appSettingsSubject.value) + ) + ) + ); + } + + /** + * Flags cache as stale and new data will be fetched from http when requested + */ + refreshCache(): void { + this._configsAge = 0; + } + + /** + * optionally override the cache TTL, only recommended for testing + * @param ttl ttl in milliseconds + */ + setCacheTTL(ttl: number): void { + this._CACHE_TIME_TO_LIVE = ttl; + } +} diff --git a/src/app/shared/testing/services.spy.ts b/src/app/shared/testing/services.spy.ts index 1e445c61..33129f36 100644 --- a/src/app/shared/testing/services.spy.ts +++ b/src/app/shared/testing/services.spy.ts @@ -23,7 +23,6 @@ import { ApiResponse } from "../../workers/models/api-response"; import { Worker } from "../models/worker"; import { Skill } from "../models/skill"; import { Report } from "src/app/reports/models/report"; -import { tick } from "@angular/core/testing"; export class EmployersServiceSpy { getEmployer = jasmine @@ -200,6 +199,12 @@ export class OnlineOrdersServiceSpy { export const getConfigsList = (): Config[] => { const configs: Config[] = new Array(); + configs.push( + new Config({ + key: "OnlineOrderTerms", + value: `[{"name":"term1","text":"This is the term one"},{"name":"term2","text":"This is the term two"}]`, + }) + ); configs.push(new Config({ key: "WorkCenterDescription_EN", value: "foo" })); configs.push(new Config({ key: "FacebookAppId", value: "foo" })); configs.push(new Config({ key: "GoogleClientId", value: "foo" })); @@ -220,6 +225,28 @@ export class ConfigsServiceSpy { .and.callFake(() => observableOf(getConfigsList())); } +export class ConfigsServiceClientSpy { + apiConfigsGet = jasmine + .createSpy("apiConfigsGet") + .and.callFake(() => observableOf(getConfigsList()[0])); + apiConfigsIdPut = jasmine + .createSpy("apiConfigsIdPut") + .and.callFake(() => observableOf(getConfigsList()[0])); +} + +export class AppSettingsStoreServiceSpy { + all$ = observableOf(getConfigsList()); + getConfigsWith = jasmine + .createSpy("getConfigsWith") + .and.callFake(() => observableOf(getConfigsList())); + getConfig = jasmine + .createSpy("getConfig") + .and.callFake(() => observableOf(getConfigsList()[0])); + update = jasmine + .createSpy("update") + .and.callFake(() => observableOf(getConfigsList[0])); +} + /** * the spy for PrimeNG message service */ diff --git a/src/app/welcome/welcome.component.spec.ts b/src/app/welcome/welcome.component.spec.ts index 9eda53c7..ca3e6fd2 100644 --- a/src/app/welcome/welcome.component.spec.ts +++ b/src/app/welcome/welcome.component.spec.ts @@ -3,17 +3,17 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { WelcomeComponent } from "./welcome.component"; -import { ConfigsService } from "../configs/configs.service"; import { AuthService } from "../shared/index"; import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRouteSpy, + AppSettingsStoreServiceSpy, AuthServiceSpy, - ConfigsServiceSpy, getConfigsList, RouterSpy, } from "../shared/testing"; import { MessageService } from "primeng/api"; +import { AppSettingsStoreService } from "../shared/services/app-settings-store.service"; describe("WelcomeComponent", () => { let component: WelcomeComponent; @@ -27,7 +27,10 @@ describe("WelcomeComponent", () => { .overrideComponent(WelcomeComponent, { set: { providers: [ - { provide: ConfigsService, useClass: ConfigsServiceSpy }, + { + provide: AppSettingsStoreService, + useClass: AppSettingsStoreServiceSpy, + }, { provide: AuthService, useClass: AuthServiceSpy }, { provide: Router, useClass: RouterSpy }, { provide: ActivatedRoute, useClass: ActivatedRouteSpy }, diff --git a/src/app/welcome/welcome.component.ts b/src/app/welcome/welcome.component.ts index 55748eb4..d56fb0ee 100644 --- a/src/app/welcome/welcome.component.ts +++ b/src/app/welcome/welcome.component.ts @@ -1,5 +1,4 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; -import { ConfigsService } from "../configs/configs.service"; import { AuthService } from "../shared/index"; import { ActivatedRoute, Router } from "@angular/router"; import { environment } from "../../environments/environment"; @@ -8,7 +7,7 @@ import { User } from "../shared/models/user"; import { map, pluck, takeWhile, tap } from "rxjs/operators"; import { Observable } from "rxjs"; import { MessageService } from "primeng/api"; - +import { AppSettingsStoreService } from "../shared/services/app-settings-store.service"; enum DashboardState { None = "None", @@ -78,7 +77,7 @@ export class WelcomeComponent implements OnInit, OnDestroy { ]; constructor( - private cfgService: ConfigsService, + private appSettingsStore: AppSettingsStoreService, private authService: AuthService, private router: Router, private activatedRoute: ActivatedRoute, @@ -117,7 +116,7 @@ export class WelcomeComponent implements OnInit, OnDestroy { ngOnInit(): void { // this.handleQueryParams(); - this.cfgService.getAllConfigs().subscribe( + this.appSettingsStore.all$.subscribe( (data) => { this.serverData = data; console.log("configs: ", data); // TODO this was 2am madness, this isn"t great JS