diff --git a/public/i18n/en.json b/public/i18n/en.json index 84c8541..b17364f 100644 --- a/public/i18n/en.json +++ b/public/i18n/en.json @@ -28,10 +28,13 @@ "edit_chain": "edit chain" }, "establishment": { - "establishments":"establishments" + "establishments":"establishments", + "new_establishment": "new establishment", + "edit_establishment": "edit establishment" } }, "common": { + "address":"address", "name":"name", "save": "save", "update": "update", diff --git a/public/i18n/es.json b/public/i18n/es.json index e3ae7db..9b0fa26 100644 --- a/public/i18n/es.json +++ b/public/i18n/es.json @@ -28,10 +28,13 @@ "edit_chain": "editar cadena" }, "establishment": { - "establishments":"establecimientos" + "establishments":"establecimientos", + "new_establishment": "nuevo establecimiento", + "edit_establishment": "editar establecimiento" } }, "common": { + "address":"dirección", "name":"nombre", "no_file_yet": "Sin carga", "save": "guardar", diff --git a/public/i18n/pt.json b/public/i18n/pt.json index 84c8541..b17364f 100644 --- a/public/i18n/pt.json +++ b/public/i18n/pt.json @@ -28,10 +28,13 @@ "edit_chain": "edit chain" }, "establishment": { - "establishments":"establishments" + "establishments":"establishments", + "new_establishment": "new establishment", + "edit_establishment": "edit establishment" } }, "common": { + "address":"address", "name":"name", "save": "save", "update": "update", diff --git a/src/app/components/establishment-add/establishment-add.spec.ts b/src/app/components/establishment-add/establishment-add.spec.ts new file mode 100644 index 0000000..a559207 --- /dev/null +++ b/src/app/components/establishment-add/establishment-add.spec.ts @@ -0,0 +1,52 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EstablishmentAdd } from './establishment-add'; +import { provideTranslateService } from '@ngx-translate/core'; +import { Chain } from '../../models/Chain'; +import { Establishment } from '../../models/Establishment'; +import { EstablishmentSettings } from '../../services/establishment-settings'; +import { EstablishmentForm } from '../../pages/settings/establishments/establishment-form/establishment-form'; + +describe('EstablishmentAdd', () => { + let component: EstablishmentAdd; + let fixture: ComponentFixture; + + let establishmentSettings: Partial; + + beforeEach(async () => { + establishmentSettings = { + save: vi.fn(), + }; + TestBed.overrideComponent(EstablishmentForm, { + set: { template: `` }, + }); + await TestBed.configureTestingModule({ + imports: [EstablishmentAdd], + providers: [ + provideTranslateService(), + { provide: EstablishmentSettings, useValue: establishmentSettings }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(EstablishmentAdd); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should save on valid form', async () => { + const SELECTED_CHAIN_MOCK = new Chain('Mock', 'mock_logo.png', 1); + const ADDRESS_MOCK = 'Mock street'; + await component.submit(); + //form is invalid so it doesnt call establishmenDAO save method + expect(establishmentSettings.save).not.toHaveBeenCalled(); + //User chooses chain, form is valid + component['form'].patchValue({ chain: SELECTED_CHAIN_MOCK, address: ADDRESS_MOCK }); + await component.submit(); + const establishment = new Establishment(SELECTED_CHAIN_MOCK, ADDRESS_MOCK); + expect(establishmentSettings.save).toHaveBeenCalledExactlyOnceWith(establishment); + }); +}); diff --git a/src/app/components/establishment-add/establishment-add.ts b/src/app/components/establishment-add/establishment-add.ts new file mode 100644 index 0000000..7895a20 --- /dev/null +++ b/src/app/components/establishment-add/establishment-add.ts @@ -0,0 +1,36 @@ +import { Component, inject } from '@angular/core'; + +import { EstablishmentSettings } from '../../services/establishment-settings'; +import { Establishment } from '../../models/Establishment'; +import { ActionBtn } from '../action-btn/action-btn'; +import { TranslatePipe } from '@ngx-translate/core'; +import { UpperfirstPipe } from '../../pipes/upperfirst-pipe'; +import { EstablishmentFormGroup } from '../../pages/settings/establishments/establishment-formgroup'; +import { SettingsBaseAddEdit } from '../settings-base-add-edit/settings-base-add-edit'; + +@Component({ + selector: 'app-establishment-add', + imports: [ + ActionBtn, + TranslatePipe, + UpperfirstPipe, + ], + templateUrl: './../settings-base-add-edit/settings-base-add-edit.html', + styleUrl: './../settings-base-add-edit/settings-base-add-edit.scss', +}) +export class EstablishmentAdd extends SettingsBaseAddEdit { + private readonly establishmentSettings = inject(EstablishmentSettings); + + readonly form = new EstablishmentFormGroup(); + btnText = 'common.save'; + title = 'settings.establishment.new_establishment'; + + async submit() { + const chain = this.form.controls.chain.value; + const address = this.form.controls.address.value; + if (chain?.id) { + const establishment = new Establishment(chain, address); + this.establishmentSettings.save(establishment); + } + } +} diff --git a/src/app/components/establishment-edit/establishment-edit.spec.ts b/src/app/components/establishment-edit/establishment-edit.spec.ts new file mode 100644 index 0000000..815b3c5 --- /dev/null +++ b/src/app/components/establishment-edit/establishment-edit.spec.ts @@ -0,0 +1,62 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EstablishmentEdit } from './establishment-edit'; +import { ActivatedRoute } from '@angular/router'; +import { BehaviorSubject } from 'rxjs'; +import { Establishment } from '../../models/Establishment'; +import { Chain } from '../../models/Chain'; +import { EstablishmentSettings } from '../../services/establishment-settings'; +import { provideTranslateService } from '@ngx-translate/core'; +import { EstablishmentForm } from '../../pages/settings/establishments/establishment-form/establishment-form'; +import { By } from '@angular/platform-browser'; + +describe('EstablishmentEdit', () => { + let component: EstablishmentEdit; + let fixture: ComponentFixture; + + let activatedRoute: Partial; + let establishmentSettings: Partial; + + const data = new BehaviorSubject({ + establishment: new Establishment(new Chain('mock', 'logo_mock.jpg', 1), 'mock street', 1), + }); + beforeEach(async () => { + activatedRoute = { + data, + }; + establishmentSettings = { + update: vi.fn(), + }; + + TestBed.overrideComponent(EstablishmentForm, { + set: { template: `` }, + }); + await TestBed.configureTestingModule({ + imports: [EstablishmentEdit], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: EstablishmentSettings, useValue: establishmentSettings }, + provideTranslateService(), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(EstablishmentEdit); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call establishmentSettings update', async () => { + const CHAIN_MOCK = new Chain('mock', 'logo_mock.png', 2); + const ADDRESS_MOCK = 'mock street'; + //User selects chain + component['form'].patchValue({chain: CHAIN_MOCK, address: ADDRESS_MOCK}); + const submitBtn = fixture.debugElement.query(By.css('app-action-btn')); + submitBtn.triggerEventHandler('click'); + await fixture.whenStable() + expect(establishmentSettings.update).toHaveBeenCalledExactlyOnceWith(new Establishment(CHAIN_MOCK, ADDRESS_MOCK, component['establishment']!.id)); + }); +}); diff --git a/src/app/components/establishment-edit/establishment-edit.ts b/src/app/components/establishment-edit/establishment-edit.ts new file mode 100644 index 0000000..657abeb --- /dev/null +++ b/src/app/components/establishment-edit/establishment-edit.ts @@ -0,0 +1,53 @@ +import { Component, inject, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { UpperfirstPipe } from '../../pipes/upperfirst-pipe'; +import { TranslatePipe } from '@ngx-translate/core'; +import { Establishment } from '../../models/Establishment'; +import { Observable, take, tap } from 'rxjs'; +import { EstablishmentFormGroup } from '../../pages/settings/establishments/establishment-formgroup'; +import { ActionBtn } from '../action-btn/action-btn'; +import { EstablishmentSettings } from '../../services/establishment-settings'; +import { SettingsBaseAddEdit } from '../settings-base-add-edit/settings-base-add-edit'; + +@Component({ + selector: 'app-establishment-edit', + imports: [UpperfirstPipe, TranslatePipe, ActionBtn], + templateUrl: './../settings-base-add-edit/settings-base-add-edit.html', + styleUrl: './../settings-base-add-edit/settings-base-add-edit.scss', +}) +export class EstablishmentEdit extends SettingsBaseAddEdit implements OnInit { + private readonly activatedRoute = inject(ActivatedRoute); + private readonly establishmentSettings = inject(EstablishmentSettings); + + private establishment?: Establishment; + readonly form = new EstablishmentFormGroup(); + btnText = 'common.update'; + title = 'settings.establishment.edit_establishment' + + ngOnInit() { + (>this.activatedRoute.data) + .pipe( + take(1), + tap((data) => (this.establishment = data.establishment)), + tap((data) => this.patchForm(data.establishment)), + ) + .subscribe(); + } + + patchForm(establishment: Establishment) { + this.form.patchValue({ + chain: establishment.chain, + address: establishment.address, + }); + } + + async submit() { + const chain = this.form.controls.chain.value; + if (chain && this.establishment?.id) { + const address = this.form.controls.address.value; + const establishment = new Establishment(chain, address, this.establishment.id); + await this.establishmentSettings.update(establishment); + } + } + +} diff --git a/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.html b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.html new file mode 100644 index 0000000..d7e5760 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.scss b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.scss new file mode 100644 index 0000000..6a25b44 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; +} \ No newline at end of file diff --git a/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.spec.ts b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.spec.ts new file mode 100644 index 0000000..16b754c --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EstablishmentAddPage } from './establishment-add-page'; +import { EstablishmentSettings } from '../../../../services/establishment-settings'; +import { provideTranslateService } from '@ngx-translate/core'; +import { ChainDAO } from '../../../../dao/ChainDAO'; + +describe('EstablishmentAddPage', () => { + let component: EstablishmentAddPage; + let fixture: ComponentFixture; + + let chainDAO: Partial; + + beforeEach(async () => { + chainDAO = { + findAll: vi.fn(), + }; + await TestBed.configureTestingModule({ + imports: [EstablishmentAddPage], + providers: [ + { provide: ChainDAO, useValue: chainDAO }, + { provide: EstablishmentSettings, useValue: {} }, + provideTranslateService(), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(EstablishmentAddPage); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.ts b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.ts new file mode 100644 index 0000000..91b13b3 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-add-page/establishment-add-page.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { EstablishmentAdd } from "../../../../components/establishment-add/establishment-add"; +import { EstablishmentForm } from "../establishment-form/establishment-form"; + +@Component({ + selector: 'app-establishment-add-page', + imports: [EstablishmentAdd, EstablishmentForm], + templateUrl: './establishment-add-page.html', + styleUrl: './establishment-add-page.scss', +}) +export class EstablishmentAddPage { + +} diff --git a/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.html b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.html new file mode 100644 index 0000000..b51e4af --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.scss b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.scss new file mode 100644 index 0000000..2e20835 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.scss @@ -0,0 +1,9 @@ +:host { + display: flex; + flex-direction: column; + height: 100%; +} + +h3 { + margin-top: 0; +} \ No newline at end of file diff --git a/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.spec.ts b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.spec.ts new file mode 100644 index 0000000..746ae6b --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.spec.ts @@ -0,0 +1,45 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EstablishmentEditPage } from './establishment-edit-page'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; +import { Establishment } from '../../../../models/Establishment'; +import { Chain } from '../../../../models/Chain'; +import { EstablishmentSettings } from '../../../../services/establishment-settings'; +import { provideTranslateService } from '@ngx-translate/core'; +import { ChainDAO } from '../../../../dao/ChainDAO'; + +describe('EstablishmentEditPage', () => { + let component: EstablishmentEditPage; + let fixture: ComponentFixture; + + let activatedRoute: Partial; + let chainDAO: Partial; + + beforeEach(async () => { + activatedRoute = { + data: of({establishment: new Establishment(new Chain('mock', 'logo_mock.jpg', 1), 'mock street', 1)}), + }; + chainDAO = { + findAll: vi.fn().mockResolvedValue([]), + }; + + await TestBed.configureTestingModule({ + imports: [EstablishmentEditPage], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: ChainDAO, useValue: chainDAO }, + { provide: EstablishmentSettings, useValue: {} }, + provideTranslateService(), + ], + }).compileComponents(); + + fixture = TestBed.createComponent(EstablishmentEditPage); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.ts b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.ts new file mode 100644 index 0000000..8dfe0ce --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-edit-page/establishment-edit-page.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { EstablishmentEdit } from "../../../../components/establishment-edit/establishment-edit"; +import { EstablishmentForm } from "../establishment-form/establishment-form"; + +@Component({ + selector: 'app-establishment-edit-page', + imports: [EstablishmentEdit, EstablishmentForm], + templateUrl: './establishment-edit-page.html', + styleUrl: './establishment-edit-page.scss', +}) +export class EstablishmentEditPage { + +} diff --git a/src/app/pages/settings/establishments/establishment-form/establishment-form.html b/src/app/pages/settings/establishments/establishment-form/establishment-form.html new file mode 100644 index 0000000..254207d --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-form/establishment-form.html @@ -0,0 +1,7 @@ +
+ + + {{'common.address'|translate|upperfirst}} + + +
\ No newline at end of file diff --git a/src/app/pages/settings/establishments/establishment-form/establishment-form.spec.ts b/src/app/pages/settings/establishments/establishment-form/establishment-form.spec.ts new file mode 100644 index 0000000..ee06970 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-form/establishment-form.spec.ts @@ -0,0 +1,31 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EstablishmentForm } from './establishment-form'; +import { ChainDAO } from '../../../../dao/ChainDAO'; +import { provideTranslateService } from '@ngx-translate/core'; + +describe('EstablishmentForm', () => { + let component: EstablishmentForm; + let fixture: ComponentFixture; + + let chainDAO: Partial; + + beforeEach(async () => { + chainDAO = { + findAll: vi.fn().mockResolvedValue([]), + }; + + await TestBed.configureTestingModule({ + imports: [EstablishmentForm], + providers: [{ provide: ChainDAO, useValue: chainDAO }, provideTranslateService()], + }).compileComponents(); + + fixture = TestBed.createComponent(EstablishmentForm); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/settings/establishments/establishment-form/establishment-form.ts b/src/app/pages/settings/establishments/establishment-form/establishment-form.ts new file mode 100644 index 0000000..d5f2d00 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-form/establishment-form.ts @@ -0,0 +1,18 @@ +import { Component, input } from '@angular/core'; +import { EstablishmentFormGroup } from '../establishment-formgroup'; +import { ChainSelect } from '../../../../components/chain-select/chain-select'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatFormField, MatLabel } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; +import { TranslatePipe } from '@ngx-translate/core'; +import { UpperfirstPipe } from "../../../../pipes/upperfirst-pipe"; + +@Component({ + selector: 'app-establishment-form', + imports: [ChainSelect, ReactiveFormsModule, MatFormField, MatInput, MatLabel, TranslatePipe, UpperfirstPipe], + templateUrl: './establishment-form.html', + styles: '', +}) +export class EstablishmentForm { + form = input(new EstablishmentFormGroup()); +} diff --git a/src/app/pages/settings/establishments/establishment-formgroup.ts b/src/app/pages/settings/establishments/establishment-formgroup.ts new file mode 100644 index 0000000..1d3e589 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-formgroup.ts @@ -0,0 +1,16 @@ +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { Chain } from '../../../models/Chain'; + +export class EstablishmentFormGroup extends FormGroup<{ + chain: FormControl; + address: FormControl; +}> { + constructor( + form = { + chain: new FormControl(null, [Validators.required]), + address: new FormControl('', { nonNullable: true }), + }, + ) { + super(form); + } +} diff --git a/src/app/pages/settings/establishments/establishment-list/establishment-list.spec.ts b/src/app/pages/settings/establishments/establishment-list/establishment-list.spec.ts new file mode 100644 index 0000000..73e31d7 --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-list/establishment-list.spec.ts @@ -0,0 +1,49 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EstablishmentList } from './establishment-list'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BehaviorSubject } from 'rxjs'; +import { Establishment } from '../../../../models/Establishment'; +import { Chain } from '../../../../models/Chain'; +import { By } from '@angular/platform-browser'; + +const ADD_PATH = ['settings', 'establishments', 'add']; +describe('EstablishmentList', () => { + let component: EstablishmentList; + let fixture: ComponentFixture; + + let activatedRoute: Partial; + let router: Partial; + + const activatedRouteData = new BehaviorSubject({ + establishments: [new Establishment(new Chain('Mock', 'logo_mock.png', 1), 'Mock street', 1)], + }); + + beforeEach(async () => { + activatedRoute = { data: activatedRouteData.asObservable() }; + router = { + navigate: vi.fn(), + }; + await TestBed.configureTestingModule({ + imports: [EstablishmentList], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: Router, useValue: router }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(EstablishmentList); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should navigate to add on add btn click', () => { + const addBtn = fixture.debugElement.query(By.css('app-floating-big-btn')); + addBtn.triggerEventHandler('bigClick'); + expect(router.navigate).toHaveBeenCalledExactlyOnceWith(ADD_PATH); + }); +}); diff --git a/src/app/pages/settings/establishments/establishment-list/establishment-list.ts b/src/app/pages/settings/establishments/establishment-list/establishment-list.ts new file mode 100644 index 0000000..33dd5dd --- /dev/null +++ b/src/app/pages/settings/establishments/establishment-list/establishment-list.ts @@ -0,0 +1,40 @@ +import { Component, inject } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SimpleListWActions } from '../../../../components/simple-list-w-actions/simple-list-w-actions'; +import { FloatingBigBtn } from '../../../../components/floating-big-btn/floating-big-btn'; +import { AsyncPipe } from '@angular/common'; +import { map } from 'rxjs'; +import { SimpleListItem } from '../../../../components/simple-list-w-actions/SimpleListItem'; +import { SimpleListItemAction } from '../../../../components/simple-list-w-actions/SimpleListItemAction'; +import { Establishment } from '../../../../models/Establishment'; +import { SettingsBaseList } from '../../../../components/settings-base-list/settings-base-list'; + +@Component({ + selector: 'app-establishment-list', + imports: [SimpleListWActions, FloatingBigBtn, AsyncPipe], + templateUrl: './../../../../components/settings-base-list/settings-base-list.html', + styleUrl: './../../../../components/settings-base-list/settings-base-list.scss' +}) +export class EstablishmentList extends SettingsBaseList{ + activatedRoute = inject(ActivatedRoute); + router = inject(Router); + + data$ = this.activatedRoute.data.pipe( + map((data) => + (data['establishments']).map((e) => { + const itemName = e.chain.name + (e.address ? ` - ${e.address}` : ''); + return new SimpleListItem(String(e.id), itemName ?? '', [ + new SimpleListItemAction('edit', 'edit'), + ]); + }), + ), + ); + + edit(action: { action: string; subject: string }) { + this.router.navigate(['settings', 'establishments', 'edit', action.subject]); + } + + add() { + this.router.navigate(['settings', 'establishments', 'add']); + } +} diff --git a/src/app/pages/settings/establishments/establishments.html b/src/app/pages/settings/establishments/establishments.html new file mode 100644 index 0000000..0d5cdf5 --- /dev/null +++ b/src/app/pages/settings/establishments/establishments.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/app/pages/settings/establishments/establishments.spec.ts b/src/app/pages/settings/establishments/establishments.spec.ts new file mode 100644 index 0000000..f388678 --- /dev/null +++ b/src/app/pages/settings/establishments/establishments.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { Establishments } from './establishments'; +import { provideTranslateService } from '@ngx-translate/core'; + +describe('Establishments', () => { + let component: Establishments; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [Establishments], + providers: [provideTranslateService()] + }) + .compileComponents(); + + fixture = TestBed.createComponent(Establishments); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/settings/establishments/establishments.ts b/src/app/pages/settings/establishments/establishments.ts new file mode 100644 index 0000000..dba68aa --- /dev/null +++ b/src/app/pages/settings/establishments/establishments.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; +import { SimpleLayout } from "../../../components/simple-layout/simple-layout"; +import { RouterOutlet } from "@angular/router"; + +@Component({ + selector: 'app-establishments', + imports: [SimpleLayout, RouterOutlet], + templateUrl: './establishments.html', + styles: '', +}) +export class Establishments { + +} diff --git a/src/app/pages/settings/settings.route.ts b/src/app/pages/settings/settings.route.ts index 60115ae..7a3ca53 100644 --- a/src/app/pages/settings/settings.route.ts +++ b/src/app/pages/settings/settings.route.ts @@ -2,6 +2,9 @@ import { Route } from '@angular/router'; import { chainsResolver } from '../../resolvers/chains-resolver'; import { ChainList } from './chains/chain-list/chain-list'; import { chainResolver } from '../../resolvers/chain-resolver'; +import { EstablishmentList } from './establishments/establishment-list/establishment-list'; +import { establishmentsResolver } from '../../resolvers/establishments-resolver'; +import { establishmentResolver } from '../../resolvers/establishment-resolver'; export const routes: Route[] = [ { @@ -32,4 +35,24 @@ export const routes: Route[] = [ } ] }, + { + path: 'establishments', + loadComponent: () => import('./establishments/establishments').then( c => c.Establishments ), + children: [ + { + path: '', + component: EstablishmentList, + resolve: {establishments: establishmentsResolver}, + }, + { + path: 'add', + loadComponent: () => import('./establishments/establishment-add-page/establishment-add-page').then(c => c.EstablishmentAddPage) + }, + { + path: 'edit/:id', + loadComponent: () => import('./establishments/establishment-edit-page/establishment-edit-page').then(c => c.EstablishmentEditPage), + resolve: {establishment: establishmentResolver} + } + ] + }, ]; diff --git a/src/app/pages/settings/settings.ts b/src/app/pages/settings/settings.ts index 5a3af8e..1165a0c 100644 --- a/src/app/pages/settings/settings.ts +++ b/src/app/pages/settings/settings.ts @@ -13,7 +13,7 @@ export class Settings { readonly menuItems = [ new IconNavListItem('translate', 'settings.nav.language', ['languages']), new IconNavListItem('warehouse', 'settings.nav.manage_chains', ['chains']), - new IconNavListItem('store', 'settings.nav.manage_establishments', ['/']), + new IconNavListItem('store', 'settings.nav.manage_establishments', ['establishments']), new IconNavListItem('shopping_bag', 'settings.nav.manage_products', ['/']), ]; } diff --git a/src/app/resolvers/establishment-resolver.spec.ts b/src/app/resolvers/establishment-resolver.spec.ts new file mode 100644 index 0000000..0c657e3 --- /dev/null +++ b/src/app/resolvers/establishment-resolver.spec.ts @@ -0,0 +1,68 @@ +import { TestBed } from '@angular/core/testing'; +import { provideRouter, RedirectCommand, ResolveFn, Router } from '@angular/router'; + +import { establishmentResolver } from './establishment-resolver'; +import { Establishment } from '../models/Establishment'; +import { Component } from '@angular/core'; +import { EstablishmentDAO } from '../dao/EstablishmentDAO'; +import { Chain } from '../models/Chain'; + +const PATH_MOCK = 'mock/:id'; + +@Component({ + selector: 'app-mock', + template: ``, + styles: [], +}) +class MockComponent {} + +const ESTABLISHMENT_SETTINGS_PATH = 'settings/establishments'; +describe('establishmentResolver', () => { + let establishmentDAO: Partial; + let router: Router; + let ESTABLISHMENT_MOCK: Establishment; + const executeResolver: ResolveFn = (...resolverParameters) => + TestBed.runInInjectionContext(() => establishmentResolver(...resolverParameters)); + + beforeEach(() => { + ESTABLISHMENT_MOCK = new Establishment(new Chain('mock', 'logo_mock.png', 1), 'mock street'); + establishmentDAO = { + findBy: vi.fn().mockResolvedValue([ESTABLISHMENT_MOCK]), + }; + TestBed.configureTestingModule({ + imports: [MockComponent], + providers: [ + provideRouter([ + { + path: 'mock/:id', + component: MockComponent, + resolve: { establishment: establishmentResolver }, + }, + { + path: ESTABLISHMENT_SETTINGS_PATH, + component: MockComponent + } + ]), + { provide: EstablishmentDAO, useValue: establishmentDAO }, + ], + }); + + router = TestBed.inject(Router); + }); + + it('should be created', () => { + expect(executeResolver).toBeTruthy(); + }); + + it('should fetch establishment by id', async () => { + const ESTABLISHMENT_ID_MOCK = 1; + await router.navigate(['mock', ESTABLISHMENT_ID_MOCK]); + expect(establishmentDAO.findBy).toHaveBeenCalledExactlyOnceWith({id: ESTABLISHMENT_ID_MOCK}) + }); + + it('should navigate back to settings if establishment is not found', async () => { + establishmentDAO.findBy = vi.fn().mockResolvedValue([]); + await router.navigate(['mock', 1]); + expect(router.url).toEqual('/'+ESTABLISHMENT_SETTINGS_PATH); + }); +}); diff --git a/src/app/resolvers/establishment-resolver.ts b/src/app/resolvers/establishment-resolver.ts new file mode 100644 index 0000000..3d4e72b --- /dev/null +++ b/src/app/resolvers/establishment-resolver.ts @@ -0,0 +1,27 @@ +import { inject } from '@angular/core'; +import { RedirectCommand, ResolveFn, Router } from '@angular/router'; +import { EstablishmentDAO } from '../dao/EstablishmentDAO'; +import { Establishment } from '../models/Establishment'; + +export const establishmentResolver: ResolveFn = async ( + route, + _, +) => { + const establishmentDAO = inject(EstablishmentDAO); + const router = inject(Router); + const establishmentID = (<{ id: string }>route.params).id; + let establishment: Establishment; + try { + const results = await establishmentDAO.findBy({ id: Number(establishmentID) }); + if (!results[0]) { + throw new Error('The search for establishment on edit did not find any match'); + } + establishment = results[0]; + } catch (e) { + console.error(e); + return new RedirectCommand(router.parseUrl('settings/establishments'), { + skipLocationChange: true, + }); + } + return establishment; +}; diff --git a/src/app/resolvers/establishments-resolver.spec.ts b/src/app/resolvers/establishments-resolver.spec.ts new file mode 100644 index 0000000..d0886ee --- /dev/null +++ b/src/app/resolvers/establishments-resolver.spec.ts @@ -0,0 +1,52 @@ +import { TestBed } from '@angular/core/testing'; +import { provideRouter, ResolveFn, Router } from '@angular/router'; + +import { establishmentsResolver } from './establishments-resolver'; +import { Establishment } from '../models/Establishment'; +import { EstablishmentDAO } from '../dao/EstablishmentDAO'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-mock', + template: ``, + styles: ``, +}) +class MockComponent {} + +const PATH_MOCK = 'establishments'; +describe('establishmentsResolver', () => { + let establishmentDAO: Partial; + let router: Router; + const executeResolver: ResolveFn = (...resolverParameters) => + TestBed.runInInjectionContext(() => establishmentsResolver(...resolverParameters)); + + beforeEach(() => { + establishmentDAO = { + findAll: vi.fn().mockResolvedValue([]), + }; + + TestBed.configureTestingModule({ + providers: [ + { provide: EstablishmentDAO, useValue: establishmentDAO }, + provideRouter([ + { + path: PATH_MOCK, + component: MockComponent, + resolve: { establishments: establishmentsResolver }, + }, + ]), + ], + }); + + router = TestBed.inject(Router); + }); + + it('should be created', () => { + expect(executeResolver).toBeTruthy(); + }); + + it('should call establishmentDAO findAll method', async () => { + await router.navigate([PATH_MOCK]); + expect(establishmentDAO.findAll).toHaveBeenCalled(); + }); +}); diff --git a/src/app/resolvers/establishments-resolver.ts b/src/app/resolvers/establishments-resolver.ts new file mode 100644 index 0000000..9b710e2 --- /dev/null +++ b/src/app/resolvers/establishments-resolver.ts @@ -0,0 +1,16 @@ +import { inject } from '@angular/core'; +import { ResolveFn } from '@angular/router'; +import { EstablishmentDAO } from '../dao/EstablishmentDAO'; +import { Establishment } from '../models/Establishment'; + +export const establishmentsResolver: ResolveFn = async (route, state) => { + const establishmentDAO = inject(EstablishmentDAO); + let establishments: Establishment[] = []; + try { + establishments = await establishmentDAO.findAll() + } catch(e) { + console.error(e); + //TODO: report error + } + return establishments; +}; diff --git a/src/app/services/establishment-settings.spec.ts b/src/app/services/establishment-settings.spec.ts new file mode 100644 index 0000000..377dabf --- /dev/null +++ b/src/app/services/establishment-settings.spec.ts @@ -0,0 +1,44 @@ +import { TestBed } from '@angular/core/testing'; + +import { EstablishmentSettings } from './establishment-settings'; +import { EstablishmentDAO } from '../dao/EstablishmentDAO'; +import { Router } from '@angular/router'; +import { Chain } from '../models/Chain'; +import { Establishment } from '../models/Establishment'; + +describe('EstablishmentSettings', () => { + let service: EstablishmentSettings; + + let establishmentDAO: Partial; + let router: Partial; + + beforeEach(() => { + establishmentDAO = { + insert: vi.fn().mockResolvedValue({ rows: [] }), + }; + router = { + navigate: vi.fn(), + }; + + TestBed.configureTestingModule({ + providers: [ + { provide: EstablishmentDAO, useValue: establishmentDAO }, + { provide: Router, useValue: router }, + ], + }); + service = TestBed.inject(EstablishmentSettings); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should insert new establishment', async () => { + const CHAIN_MOCK = new Chain('Chain mock', 'logo_mock.png', 1); + const ADDRESS_MOCK = 'Mock street'; + const ESTABLISHMENT_MOCK = new Establishment(CHAIN_MOCK, ADDRESS_MOCK); + await service.save(ESTABLISHMENT_MOCK); + expect(establishmentDAO.insert).toHaveBeenCalledExactlyOnceWith(ESTABLISHMENT_MOCK); + expect(router.navigate).toHaveBeenCalledExactlyOnceWith(['settings', 'establishments']); + }); +}); diff --git a/src/app/services/establishment-settings.ts b/src/app/services/establishment-settings.ts new file mode 100644 index 0000000..011e174 --- /dev/null +++ b/src/app/services/establishment-settings.ts @@ -0,0 +1,37 @@ +import { inject, Injectable } from '@angular/core'; +import { Location } from '@angular/common'; +import { Router } from '@angular/router'; + +import { EstablishmentDAO } from '../dao/EstablishmentDAO'; +import { Establishment } from '../models/Establishment'; + +@Injectable({ + providedIn: 'root', +}) +export class EstablishmentSettings { + private readonly establishmentDAO = inject(EstablishmentDAO); + private readonly router = inject(Router); + private readonly location = inject(Location); + + async save(establishment: Establishment) { + try { + await this.establishmentDAO.insert(establishment); + } catch(e) { + console.error(e) + //TODO: handle error + } + this.router.navigate(['settings', 'establishments']); + this.location.replaceState('settings'); + } + + async update(establishment: Establishment) { + try { + await this.establishmentDAO.update(establishment, {id: establishment.id}); + }catch(e) { + console.error(e) + //TODO: handle error + } + this.router.navigate(['settings', 'establishments']); + this.location.replaceState('settings'); + } +}