diff --git a/.gitignore b/.gitignore index 2aed783..a1e005f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ Thumbs.db /temp /resources .vercel + +.angular diff --git a/CHANGELOG.md b/CHANGELOG.md index c35694b..eebd9f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ > All notable changes to this project will be documented in this file. +## [0.7.0] - 2022-10-30 + +- Add second phase support for transportation problem on API and UI side +- UI style fixes +- Fix layout jumps caused by clearing the results when calculating a new case. +- Fix not supporting safe area view + ## [0.6.1] - 2022-09-01 - Fix showing progress bar when switching between pages diff --git a/apps/backend/src/app/controllers/transport-problem/utils/tests/transport.util.spec.ts b/apps/backend/src/app/controllers/transport-problem/utils/tests/transport.util.spec.ts index c3b043d..29e2377 100644 --- a/apps/backend/src/app/controllers/transport-problem/utils/tests/transport.util.spec.ts +++ b/apps/backend/src/app/controllers/transport-problem/utils/tests/transport.util.spec.ts @@ -1,34 +1,8 @@ -import {TransportTable} from '@opres/shared/types'; +import {resultTableMock} from '@opres/shared/data/mocks'; import {transport} from '../transport.util'; describe('TransportUtil', () => { - const resultTableMock: TransportTable = [ - { - '0': {cost: 8}, - '1': {cost: 7}, - '2': {cost: 3}, - '3': {cost: 2, transported: 15}, - }, - { - '0': {cost: 1, transported: 18}, - '1': {cost: 4}, - '2': {cost: 2, transported: 25}, - '3': {cost: 5}, - }, - { - '0': {cost: 2}, - '1': {cost: 3, transported: 13}, - '2': {cost: 4, transported: 10}, - '3': {cost: 7, transported: 5}, - }, - { - '0': {cost: 1}, - '1': {cost: 1, transported: 19}, - '2': {cost: 4}, - '3': {cost: 4}, - }, - ]; const demandsMock = [0, 0, 10, 5]; const stocksMock = [0, 0, 15, 0]; const indexMocks = 2; diff --git a/apps/frontend/src/app/components/info-card/info-card.style.scss b/apps/frontend/src/app/components/info-card/info-card.style.scss index 04ceffc..00021c4 100644 --- a/apps/frontend/src/app/components/info-card/info-card.style.scss +++ b/apps/frontend/src/app/components/info-card/info-card.style.scss @@ -1,4 +1,4 @@ -@import 'apps/frontend/src/app/styles/variables/colors'; +@import 'libs/shared/styles/colors'; mat-card { display: flex; diff --git a/apps/frontend/src/app/components/layout/layout.style.scss b/apps/frontend/src/app/components/layout/layout.style.scss index f31ad21..31ef9b8 100644 --- a/apps/frontend/src/app/components/layout/layout.style.scss +++ b/apps/frontend/src/app/components/layout/layout.style.scss @@ -1,4 +1,4 @@ -@import '../../styles/variables/colors'; +@import 'libs/shared/styles/colors'; .layout-wrapper { display: flex; @@ -42,6 +42,14 @@ box-sizing: border-box; } } + + mat-progress-bar { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 10; + } } .selected { diff --git a/apps/frontend/src/app/pages/assignment-problem/assignment-problem.style.scss b/apps/frontend/src/app/pages/assignment-problem/assignment-problem.style.scss index c90a9e9..d31c4bc 100644 --- a/apps/frontend/src/app/pages/assignment-problem/assignment-problem.style.scss +++ b/apps/frontend/src/app/pages/assignment-problem/assignment-problem.style.scss @@ -1,4 +1,4 @@ -@import '../../styles/variables/colors'; +@import 'libs/shared/styles/colors'; form { padding: 16px env(safe-area-inset-right) 16px 16px; diff --git a/apps/frontend/src/app/pages/home/home.style.scss b/apps/frontend/src/app/pages/home/home.style.scss index 9a95b74..ed0a32a 100644 --- a/apps/frontend/src/app/pages/home/home.style.scss +++ b/apps/frontend/src/app/pages/home/home.style.scss @@ -1,4 +1,4 @@ -@import '../../styles/variables/colors'; +@import 'libs/shared/styles/colors'; :host { overflow-x: hidden; diff --git a/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.component.spec.ts b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.component.spec.ts new file mode 100644 index 0000000..dc122f8 --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.component.spec.ts @@ -0,0 +1,91 @@ +import {HttpClientModule} from '@angular/common/http'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {MatStepperModule} from '@angular/material/stepper'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; + +import { + InputTableModule, + InputTableService, + TableModule, +} from '@opres/ui/tables'; +import {TranslateModule} from '@ngx-translate/core'; + +import {AllTabModule} from '../+tabs/all-tab/all.tab.module'; +import {TabsModule} from '../+tabs/tabs.module'; +import {TransportProblemService} from '../transport-problem.service'; + +import {FirstPhaseResultInputComponent} from './first-phase-result-input.component'; + +describe('FirstPhaseResultInputComponent', () => { + let fixture: ComponentFixture; + let component: FirstPhaseResultInputComponent; + let inputTableService: InputTableService; + const firstStepFormGroupMock = { + shops: 4, + storages: 4, + }; + const costsMock = [ + {'0': 8, '1': 7, '2': 3, '3': 2}, + {'0': 1, '1': 4, '2': 2, '3': 5}, + {'0': 2, '1': 3, '2': 4, '3': 7}, + {'0': 1, '1': 1, '2': 4, '3': 4}, + ]; + const transportationsMock = [ + {'0': null, '1': null, '2': null, '3': 15}, + {'0': 8, '1': null, '2': 35, '3': null}, + {'0': 10, '1': 13, '2': null, '3': 5}, + {'0': null, '1': 19, '2': null, '3': null}, + ]; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [FirstPhaseResultInputComponent], + imports: [ + AllTabModule, + HttpClientModule, + InputTableModule, + MatStepperModule, + NoopAnimationsModule, + TableModule, + TabsModule, + TranslateModule.forRoot(), + ], + providers: [TransportProblemService], + }); + + fixture = TestBed.createComponent(FirstPhaseResultInputComponent); + component = fixture.componentInstance; + inputTableService = TestBed.inject(InputTableService); + }); + + it('should be created', () => + expect(component).toBeInstanceOf(FirstPhaseResultInputComponent)); + + it('should check default values', (done) => { + expect(component.firstStepFormGroup.getRawValue()).toEqual( + firstStepFormGroupMock, + ); + expect(component.secondStepFormGroup.getRawValue()).toEqual({}); + expect(component.costs$.getValue()).toEqual(costsMock); + expect(component.transportations$.getValue()).toEqual(transportationsMock); + component.isLoading$.subscribe((loading: boolean) => + expect(loading).toEqual(false), + ); + done(); + }); + + it('should change values of tables', () => { + component.onTransportationsChange([]); + expect(component.transportations$.getValue()).toEqual([]); + component.onCostsChange([]); + expect(component.costs$.getValue()).toEqual([]); + }); + + it('should clear tables', () => { + const tableClearSpy = jest.spyOn(inputTableService, 'clear'); + component.onTransportationsClear(); + expect(tableClearSpy).toHaveBeenCalledWith('transportations'); + component.onCostsClear(); + expect(tableClearSpy).toHaveBeenCalledWith('costs'); + }); +}); diff --git a/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.component.ts b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.component.ts new file mode 100644 index 0000000..557423b --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.component.ts @@ -0,0 +1,121 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Output, +} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; + +import {Table, TransportTable} from '@opres/shared/types'; +import {InputTableService} from '@opres/ui/tables'; +import {LanguageSwitcherService} from '@frontend/components/layout/language-switcher/language-switcher.service'; +import {LoadingHandlerService} from '@frontend/services/loading-handler.service'; +import {forEach, mapValues} from 'lodash'; +import {BehaviorSubject} from 'rxjs'; + +import { + transportProblemCacheBuster$, + TransportProblemService, +} from '../transport-problem.service'; + +@Component({ + selector: 'first-phase-result-input', + templateUrl: './first-phase-result-input.template.html', + styleUrls: [ + '../+tabs/tabs.style.scss', + './first-phase-result-input.style.scss', + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FirstPhaseResultInputComponent { + @Output() public calculate = new EventEmitter(); + public firstStepFormGroup: FormGroup; + public secondStepFormGroup: FormGroup; + public costs$ = new BehaviorSubject([ + {'0': 8, '1': 7, '2': 3, '3': 2}, + {'0': 1, '1': 4, '2': 2, '3': 5}, + {'0': 2, '1': 3, '2': 4, '3': 7}, + {'0': 1, '1': 1, '2': 4, '3': 4}, + ]); + public transportations$ = new BehaviorSubject
([ + {'0': null, '1': null, '2': null, '3': 15}, + {'0': 8, '1': null, '2': 35, '3': null}, + {'0': 10, '1': 13, '2': null, '3': 5}, + {'0': null, '1': 19, '2': null, '3': null}, + ]); + public isLoading$ = this.loadingHandler.isLoading; + + constructor( + private transportProblemService: TransportProblemService, + private languageSwitcherService: LanguageSwitcherService, + private inputTableService: InputTableService, + private loadingHandler: LoadingHandlerService, + ) { + this.firstStepFormGroup = new FormGroup({ + shops: new FormControl(4, transportProblemService.tableSizeValidators), + storages: new FormControl(4, transportProblemService.tableSizeValidators), + }); + this.secondStepFormGroup = new FormGroup({}); + } + + public onCostsChange(table: Table): void { + this.costs$.next(table); + transportProblemCacheBuster$.next(); + this.validateCostsTable(); + } + + public onTransportationsChange(table: Table): void { + this.transportations$.next(table); + transportProblemCacheBuster$.next(); + this.validateTransportationsTable(); + } + + public onCostsClear(): void { + this.inputTableService.clear('costs'); + } + + public onTransportationsClear(): void { + this.inputTableService.clear('transportations'); + } + + public onCalculate(): void { + this.calculate.emit(this.getTransportTableFromCurrentInput()); + } + + private getTransportTableFromCurrentInput(): TransportTable { + const transportations = this.transportations$.getValue(); + return this.costs$.getValue().map((row, index) => { + return mapValues(row, (cost, key) => ({ + cost: cost as number, + transported: transportations[index][key] || undefined, + })); + }); + } + + private validateCostsTable(): void { + const costs = this.costs$.getValue(); + let hasNullCost = false; + + for (const row of costs) + forEach(row, (cost) => { + if (cost === null || cost === undefined) hasNullCost = true; + }); + + if (hasNullCost) this.firstStepFormGroup.setErrors({nullCost: true}); + else this.firstStepFormGroup.setErrors(null); + } + + private validateTransportationsTable(): void { + const transportations = this.transportations$.getValue(); + let hasTransported = false; + + for (const row of transportations) + forEach(row, (transport) => { + if (transport) hasTransported = true; + }); + + if (!hasTransported) + this.secondStepFormGroup.setErrors({noTransport: true}); + else this.secondStepFormGroup.setErrors(null); + } +} diff --git a/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.module.ts b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.module.ts new file mode 100644 index 0000000..5f244ff --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.module.ts @@ -0,0 +1,15 @@ +import {NgModule} from '@angular/core'; +import {MatStepperModule} from '@angular/material/stepper'; + +import {InputTableModule, TableModule} from '@opres/ui/tables'; + +import {TabsModule} from '../+tabs/tabs.module'; + +import {FirstPhaseResultInputComponent} from './first-phase-result-input.component'; + +@NgModule({ + declarations: [FirstPhaseResultInputComponent], + imports: [InputTableModule, MatStepperModule, TableModule, TabsModule], + exports: [FirstPhaseResultInputComponent], +}) +export class FirstPhaseResultInputModule {} diff --git a/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.style.scss b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.style.scss new file mode 100644 index 0000000..d17ecbb --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.style.scss @@ -0,0 +1,10 @@ +:host { + ::ng-deep.mat-horizontal-content-container { + padding: 0; + } + + .table-wrapper { + overflow-x: auto; + padding: 8px; + } +} diff --git a/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.template.html b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.template.html new file mode 100644 index 0000000..11d2842 --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+first-phase-result-input/first-phase-result-input.template.html @@ -0,0 +1,145 @@ + + + {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.STEPS.FIRST' | + translate}} +
+
+
+ + {{'TRANSPORTATION_PROBLEM.COLUMNS_SELECT' | + translate}} + + + + {{'TRANSPORTATION_PROBLEM.ROWS_SELECT' | translate}} + + +
+ +
+
+ {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.ERRORS.NULL_COST' | + translate}} +
+
+ + +
+ +
+ + {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.STEPS.SECOND' | + translate}} +
+
+ +
+
+ {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.ERRORS.NO_TRANSPORT' | + translate}} +
+
+ + + +
+ +
+ + {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.STEPS.THIRD' | + translate}} +
+
+ {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.REVIEW_INFO' | + translate}} +
+
+
+ +
+
+ + {{'CALCULATE_BUTTON' | translate}} +
+
+
diff --git a/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.component.ts b/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.component.ts index 1f3992d..424a984 100644 --- a/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.component.ts +++ b/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.component.ts @@ -14,7 +14,7 @@ import {FirstPhaseResult} from '../../transport-problem.service'; @Component({ selector: 'first-phase-steps[firstPhaseResult][language]', templateUrl: './first-phase-steps.template.html', - styleUrls: ['./first-phase-steps.style.scss'], + styleUrls: ['./first-phase-steps.style.scss', '../results.style.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class FirstPhaseStepsComponent implements AfterViewInit { diff --git a/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.style.scss b/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.style.scss index d14f2b1..a63a535 100644 --- a/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.style.scss +++ b/apps/frontend/src/app/pages/transport-problem/+results/+first-phase-steps/first-phase-steps.style.scss @@ -8,33 +8,4 @@ padding: 8px 8px 0; margin: 0; } - - .title { - text-align: center; - padding: 16px; - margin: 0; - } - - .content-wrapper { - display: flex; - justify-content: center; - flex-wrap: wrap; - padding: 8px; - - mat-card { - flex: 1; - max-width: 400px; - margin: 0; - } - } - - .table-wrapper { - padding: 8px; - display: grid; - grid-template-rows: auto auto; - grid-template-columns: auto auto; - gap: 8px; - justify-content: start; - overflow-x: auto; - } } diff --git a/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.component.ts b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.component.ts index f02b417..5dcf2e8 100644 --- a/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.component.ts +++ b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.component.ts @@ -1,8 +1,45 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + Input, + ViewChild, +} from '@angular/core'; +import {MatTabGroup} from '@angular/material/tabs'; + +import {Language} from '@frontend/components/layout/language-switcher/language-switcher.service'; + +import {SecondPhaseResult} from '../../transport-problem.service'; @Component({ - selector: 'second-phase-steps', + selector: 'second-phase-steps[secondPhaseResult][language]', templateUrl: './second-phase-steps.template.html', + styleUrls: ['./second-phase-steps.style.scss', '../results.style.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SecondPhaseStepsComponent {} +export class SecondPhaseStepsComponent implements AfterViewInit { + @Input() public secondPhaseResult: SecondPhaseResult | undefined; + @Input() public language!: Language | null; + @ViewChild('matTabGroup', {static: true}) public matTabGroup: + | MatTabGroup + | undefined; + public readonly explanationTranslateKeys = [ + 'SIDE_TABLES', + 'ORANGE_BADGES', + 'ORANGE_NUMBERS_LEFT', + 'ORANGE_BORDERED_CELL', + ]; + + public ngAfterViewInit(): void { + this.setMatTabGroupHeight(); + } + + public onSelectedTabChange(): void { + this.setMatTabGroupHeight(); + } + + private setMatTabGroupHeight(): void { + const ntvEl = this.matTabGroup?._elementRef?.nativeElement; + ntvEl.style.minHeight = ntvEl?.clientHeight + 'px'; + } +} diff --git a/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.module.ts b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.module.ts index f595c3b..58ed1fc 100644 --- a/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.module.ts +++ b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.module.ts @@ -1,9 +1,31 @@ +import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; +import {MatCardModule} from '@angular/material/card'; +import {MatDividerModule} from '@angular/material/divider'; +import {MatTabsModule} from '@angular/material/tabs'; + +import {TableModule} from '@opres/ui/tables'; +import {InfoCardModule} from '@frontend/components/info-card/info-card.module'; +import {TranslateModule} from '@ngx-translate/core'; + +import {TransportProblemPipesModule} from '../../pipes/transport-problem-pipes.module'; +import {EpsilonResultModule} from '../epsilon-result/epsilon-result.module'; import {SecondPhaseStepsComponent} from './second-phase-steps.component'; @NgModule({ declarations: [SecondPhaseStepsComponent], + imports: [ + CommonModule, + EpsilonResultModule, + InfoCardModule, + MatCardModule, + MatDividerModule, + MatTabsModule, + TableModule, + TranslateModule, + TransportProblemPipesModule, + ], exports: [SecondPhaseStepsComponent], }) export class SecondPhaseStepsModule {} diff --git a/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.style.scss b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.style.scss new file mode 100644 index 0000000..9377827 --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.style.scss @@ -0,0 +1,15 @@ +:host { + mat-divider { + margin-top: 8px; + } + + .table-explanations { + margin-bottom: 8px; + } + + .wrapper { + padding: 8px 0; + max-width: 600px; + margin: auto; + } +} diff --git a/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.template.html b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.template.html index e69de29..c26c1fc 100644 --- a/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.template.html +++ b/apps/frontend/src/app/pages/transport-problem/+results/+second-phase-steps/second-phase-steps.template.html @@ -0,0 +1,65 @@ + +

+ {{ 'TRANSPORTATION_PROBLEM.TABS.ALL.SECOND_PHASE_RESULT_TITLE' | translate: + {steps: secondPhaseResult?.steps?.length} }} +

+ + +
+ + {{'TRANSPORTATION_PROBLEM.TABS.ALL.EXPLANATION_TITLE' | + translate}} + {{explanation}} + +
+ + + +
+
+
+
+
+ + {{'TRANSPORTATION_PROBLEM.SECOND_PHASE_TABLE_EXPLANATIONS.TITLE' | + translate}} +
    +
  • + {{'TRANSPORTATION_PROBLEM.SECOND_PHASE_TABLE_EXPLANATIONS.' + + explanationTranslateKey + '.TITLE' | translate}}: {{'TRANSPORTATION_PROBLEM.SECOND_PHASE_TABLE_EXPLANATIONS.' + + explanationTranslateKey + '.DESCRIPTION' | translate}} +
  • +
+
+
+ diff --git a/apps/frontend/src/app/pages/transport-problem/+results/epsilon-result/epsilon-result.component.ts b/apps/frontend/src/app/pages/transport-problem/+results/epsilon-result/epsilon-result.component.ts index 3625127..aa6c618 100644 --- a/apps/frontend/src/app/pages/transport-problem/+results/epsilon-result/epsilon-result.component.ts +++ b/apps/frontend/src/app/pages/transport-problem/+results/epsilon-result/epsilon-result.component.ts @@ -21,6 +21,7 @@ import {Language} from '@frontend/components/layout/language-switcher/language-s } mat-card { + box-sizing: border-box; width: fit-content; max-width: 600px; margin: auto; diff --git a/apps/frontend/src/app/pages/transport-problem/+results/results.style.scss b/apps/frontend/src/app/pages/transport-problem/+results/results.style.scss new file mode 100644 index 0000000..9b21fe1 --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+results/results.style.scss @@ -0,0 +1,29 @@ +.title { + text-align: center; + padding: 16px; + margin: 0; +} + +.table-wrapper { + padding: 8px; + display: grid; + grid-template-rows: auto auto; + grid-template-columns: auto auto; + gap: 8px; + justify-content: start; + overflow-x: auto; +} + +.content-wrapper { + display: flex; + justify-content: center; + flex-wrap: wrap; + padding: 8px; + + mat-card { + flex: 1; + max-width: 400px; + min-width: 100px; + margin: 8px; + } +} diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all-tab.component.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all-tab.component.ts index a68a9b4..d981b15 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all-tab.component.ts +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all-tab.component.ts @@ -1,4 +1,4 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {ChangeDetectionStrategy, Component, OnDestroy} from '@angular/core'; import {FormControl, FormGroup, Validators} from '@angular/forms'; import {Demands, Stocks, Table, TPData, TPMethods} from '@opres/shared/types'; @@ -23,7 +23,7 @@ import {EMPTY_TP_DATA} from '../tabs.constant'; styleUrls: ['../tabs.style.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AllTabComponent { +export class AllTabComponent implements OnDestroy { public formGroup: FormGroup; public isLoading$ = this.loadingHandler.isLoading; public currentLanguage$ = this.languageSwitcherService.currentLanguage; @@ -98,4 +98,9 @@ export class AllTabComponent { this.results$.next(null); this.tpData$.next(EMPTY_TP_DATA); } + + public ngOnDestroy(): void { + this.loadingHandler.stop(); + transportProblemCacheBuster$.next(); + } } diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.module.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.module.ts index 22118df..1dc3353 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.module.ts +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.module.ts @@ -3,7 +3,6 @@ import {RouterModule, Routes} from '@angular/router'; import {FirstPhaseStepsModule} from '../../+results/+first-phase-steps/first-phase-steps.module'; import {SecondPhaseStepsModule} from '../../+results/+second-phase-steps/second-phase-steps.module'; -import {TransportProblemService} from '../../transport-problem.service'; import {TabsModule} from '../tabs.module'; import {AllTabComponent} from './all-tab.component'; @@ -18,6 +17,5 @@ const routes: Routes = [{path: '', component: AllTabComponent}]; SecondPhaseStepsModule, TabsModule, ], - providers: [TransportProblemService], }) export class AllTabModule {} diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.template.html b/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.template.html index 3016191..888c709 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.template.html +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/all-tab/all.tab.template.html @@ -5,8 +5,6 @@ >{{'TRANSPORTATION_PROBLEM.TABS.ALL.TABLE_EXPLANATION' | translate}} -

- {{'TRANSPORTATION_PROBLEM.TABS.ALL.INFO' | translate}} @@ -92,8 +90,14 @@
- + + + +
diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.spec.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.spec.ts index f815214..ca900ac 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.spec.ts +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.spec.ts @@ -1,20 +1,14 @@ import {HttpClientModule} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {MatStepperModule} from '@angular/material/stepper'; -import {NoopAnimationsModule} from '@angular/platform-browser/animations'; -import { - InputTableModule, - InputTableService, - TableModule, -} from '@opres/ui/tables'; +import {resultTableMock} from '@opres/shared/data/mocks'; import {TranslateModule} from '@ngx-translate/core'; import {of} from 'rxjs'; import {LoadingHandlerService} from '../../../../services/loading-handler.service'; +import {FirstPhaseResultInputModule} from '../../+first-phase-result-input/first-phase-result-input.module'; import {EpsilonResultModule} from '../../+results/epsilon-result/epsilon-result.module'; import {TransportProblemService} from '../../transport-problem.service'; -import {AllTabModule} from '../all-tab/all.tab.module'; import {TabsModule} from '../tabs.module'; import {EpsilonTabComponent} from './epsilon.tab.component'; @@ -23,36 +17,15 @@ describe('EpsilonTabComponent', () => { let fixture: ComponentFixture; let component: EpsilonTabComponent; let transportProblemService: TransportProblemService; - let inputTableService: InputTableService; let loadingHandler: LoadingHandlerService; - const firstStepFormGroupMock = { - shops: 4, - storages: 4, - }; - const costsMock = [ - {'0': 8, '1': 7, '2': 3, '3': 2}, - {'0': 1, '1': 4, '2': 2, '3': 5}, - {'0': 2, '1': 3, '2': 4, '3': 7}, - {'0': 1, '1': 1, '2': 4, '3': 4}, - ]; - const transportationsMock = [ - {'0': null, '1': null, '2': null, '3': 15}, - {'0': 8, '1': null, '2': 35, '3': null}, - {'0': 10, '1': 13, '2': null, '3': 5}, - {'0': null, '1': 19, '2': null, '3': null}, - ]; beforeEach(() => { TestBed.configureTestingModule({ declarations: [EpsilonTabComponent], imports: [ - AllTabModule, EpsilonResultModule, + FirstPhaseResultInputModule, HttpClientModule, - InputTableModule, - MatStepperModule, - NoopAnimationsModule, - TableModule, TabsModule, TranslateModule.forRoot(), ], @@ -62,49 +35,19 @@ describe('EpsilonTabComponent', () => { fixture = TestBed.createComponent(EpsilonTabComponent); component = fixture.componentInstance; transportProblemService = TestBed.inject(TransportProblemService); - inputTableService = TestBed.inject(InputTableService); loadingHandler = TestBed.inject(LoadingHandlerService); }); it('should be created', () => expect(component).toBeInstanceOf(EpsilonTabComponent)); - it('should check default values', (done) => { - expect(component.firstStepFormGroup.getRawValue()).toEqual( - firstStepFormGroupMock, - ); - expect(component.secondStepFormGroup.getRawValue()).toEqual({}); - expect(component.costs$.getValue()).toEqual(costsMock); - expect(component.transportations$.getValue()).toEqual(transportationsMock); - expect(component.result$.getValue()).toEqual(null); - component.isLoading$.subscribe((loading: boolean) => - expect(loading).toEqual(false), - ); - done(); - }); - - it('should change values of tables', () => { - component.onTransportationsChange([]); - expect(component.transportations$.getValue()).toEqual([]); - component.onCostsChange([]); - expect(component.costs$.getValue()).toEqual([]); - }); - - it('should clear tables', () => { - const tableClearSpy = jest.spyOn(inputTableService, 'clear'); - component.onTransportationsClear(); - expect(tableClearSpy).toHaveBeenCalledWith('transportations'); - component.onCostsClear(); - expect(tableClearSpy).toHaveBeenCalledWith('costs'); - }); - it('should calculate epsilon', (done) => { const startLoadingSpy = jest.spyOn(loadingHandler, 'start'); const getEpsilonSpy = jest .spyOn(transportProblemService, 'getEpsilonResult') .mockReturnValue(of({value: 221})); const stopLoadingSpy = jest.spyOn(loadingHandler, 'stop'); - component.onCalculate(); + component.onCalculate(resultTableMock); expect(startLoadingSpy).toHaveBeenCalled(); expect(getEpsilonSpy).toHaveBeenCalled(); expect(component.result$).not.toEqual(null); diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.ts index 343997f..050202f 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.ts +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.component.ts @@ -1,12 +1,9 @@ -import {ChangeDetectionStrategy, Component} from '@angular/core'; -import {FormControl, FormGroup} from '@angular/forms'; +import {ChangeDetectionStrategy, Component, OnDestroy} from '@angular/core'; -import {Epsilon, Table, TransportTable} from '@opres/shared/types'; -import {InputTableService} from '@opres/ui/tables'; +import {Epsilon, TransportTable} from '@opres/shared/types'; import {LanguageSwitcherService} from '@frontend/components/layout/language-switcher/language-switcher.service'; import {LoadingHandlerService} from '@frontend/services/loading-handler.service'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; -import {forEach, mapValues} from 'lodash'; import {BehaviorSubject} from 'rxjs'; import {finalize, tap} from 'rxjs/operators'; @@ -22,61 +19,18 @@ import { styleUrls: ['../tabs.style.scss', './epsilon.tab.style.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class EpsilonTabComponent { - public firstStepFormGroup: FormGroup; - public secondStepFormGroup: FormGroup; - public costs$ = new BehaviorSubject
([ - {'0': 8, '1': 7, '2': 3, '3': 2}, - {'0': 1, '1': 4, '2': 2, '3': 5}, - {'0': 2, '1': 3, '2': 4, '3': 7}, - {'0': 1, '1': 1, '2': 4, '3': 4}, - ]); - public transportations$ = new BehaviorSubject
([ - {'0': null, '1': null, '2': null, '3': 15}, - {'0': 8, '1': null, '2': 35, '3': null}, - {'0': 10, '1': 13, '2': null, '3': 5}, - {'0': null, '1': 19, '2': null, '3': null}, - ]); - public isLoading$ = this.loadingHandler.isLoading; +export class EpsilonTabComponent implements OnDestroy { public currentLanguage$ = this.languageSwitcherService.currentLanguage; public result$ = new BehaviorSubject(null); constructor( private transportProblemService: TransportProblemService, private languageSwitcherService: LanguageSwitcherService, - private inputTableService: InputTableService, private loadingHandler: LoadingHandlerService, - ) { - this.firstStepFormGroup = new FormGroup({ - shops: new FormControl(4, transportProblemService.tableSizeValidators), - storages: new FormControl(4, transportProblemService.tableSizeValidators), - }); - this.secondStepFormGroup = new FormGroup({}); - } - - public onCostsChange(table: Table): void { - this.costs$.next(table); - transportProblemCacheBuster$.next(); - this.validateCostsTable(); - } - - public onTransportationsChange(table: Table): void { - this.transportations$.next(table); - transportProblemCacheBuster$.next(); - this.validateTransportationsTable(); - } - - public onCostsClear(): void { - this.inputTableService.clear('costs'); - } - - public onTransportationsClear(): void { - this.inputTableService.clear('transportations'); - } + ) {} - public onCalculate(): void { + public onCalculate(transportTable: TransportTable): void { this.loadingHandler.start(); - const transportTable = this.getTransportTableFromCurrentInput(); this.transportProblemService .getEpsilonResult(transportTable) .pipe( @@ -87,40 +41,8 @@ export class EpsilonTabComponent { .subscribe(); } - private getTransportTableFromCurrentInput(): TransportTable { - const transportations = this.transportations$.getValue(); - return this.costs$.getValue().map((row, index) => { - return mapValues(row, (cost, key) => ({ - cost: cost as number, - transported: transportations[index][key] as number, - })); - }); - } - - private validateCostsTable(): void { - const costs = this.costs$.getValue(); - let hasNullCost = false; - - for (const row of costs) - forEach(row, (cost) => { - if (cost === null || cost === undefined) hasNullCost = true; - }); - - if (hasNullCost) this.firstStepFormGroup.setErrors({nullCost: true}); - else this.firstStepFormGroup.setErrors(null); - } - - private validateTransportationsTable(): void { - const transportations = this.transportations$.getValue(); - let hasTransported = false; - - for (const row of transportations) - forEach(row, (transport) => { - if (transport) hasTransported = true; - }); - - if (!hasTransported) - this.secondStepFormGroup.setErrors({noTransport: true}); - else this.secondStepFormGroup.setErrors(null); + public ngOnDestroy(): void { + this.loadingHandler.stop(); + transportProblemCacheBuster$.next(); } } diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.module.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.module.ts index 01bd8b6..855bbc1 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.module.ts +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.module.ts @@ -1,11 +1,8 @@ import {NgModule} from '@angular/core'; -import {MatStepperModule} from '@angular/material/stepper'; import {RouterModule, Routes} from '@angular/router'; -import {InputTableModule, TableModule} from '@opres/ui/tables'; - +import {FirstPhaseResultInputModule} from '../../+first-phase-result-input/first-phase-result-input.module'; import {EpsilonResultModule} from '../../+results/epsilon-result/epsilon-result.module'; -import {TransportProblemService} from '../../transport-problem.service'; import {TabsModule} from '../tabs.module'; import {EpsilonTabComponent} from './epsilon.tab.component'; @@ -16,12 +13,9 @@ const routes: Routes = [{path: '', component: EpsilonTabComponent}]; declarations: [EpsilonTabComponent], imports: [ EpsilonResultModule, - InputTableModule, - MatStepperModule, + FirstPhaseResultInputModule, RouterModule.forChild(routes), - TableModule, TabsModule, ], - providers: [TransportProblemService], }) export class EpsilonTabModule {} diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.style.scss b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.style.scss index 8813217..e69de29 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.style.scss +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.style.scss @@ -1,9 +0,0 @@ -:host { - ::ng-deep.mat-horizontal-content-container { - padding: 0; - } - - .table-wrapper { - overflow-x: auto; - } -} diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.template.html b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.template.html index b8cce05..01e4829 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.template.html +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/epsilon-tab/epsilon.tab.template.html @@ -1,148 +1,6 @@ - - - {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.STEPS.FIRST' | - translate}} -
-
-
- - {{'TRANSPORTATION_PROBLEM.COLUMNS_SELECT' | - translate}} - - - - {{'TRANSPORTATION_PROBLEM.ROWS_SELECT' | translate}} - - -
- -
-
- {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.ERRORS.NULL_COST' | - translate}} -
-
- - -
- -
- - {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.STEPS.SECOND' | - translate}} -
-
- -
-
- {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.ERRORS.NO_TRANSPORT' | - translate}} -
-
- - - -
- -
- - {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.STEPS.THIRD' | - translate}} -
-
- {{'TRANSPORTATION_PROBLEM.TABS.EPSILON.REVIEW_INFO' | - translate}} -
-
-
- -
-
- - {{'CALCULATE_BUTTON' | translate}} -
-
-
+ (null); + public currentLanguage$ = this.languageSwitcherService.currentLanguage; + + constructor( + private transportProblemService: TransportProblemService, + private languageSwitcherService: LanguageSwitcherService, + private loadingHandler: LoadingHandlerService, + ) {} + + public onCalculate(transportTable: TransportTable): void { + this.loadingHandler.start(); + this.transportProblemService + .getSecondPhaseResult(transportTable) + .pipe( + tap((response) => this.result$.next(response)), + finalize(() => this.loadingHandler.stop()), + untilDestroyed(this), + ) + .subscribe(); + } + + public ngOnDestroy(): void { + this.loadingHandler.stop(); + transportProblemCacheBuster$.next(); + } +} diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.module.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.module.ts index e0b617f..789ed10 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.module.ts +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.module.ts @@ -1,7 +1,8 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {TransportProblemService} from '../../transport-problem.service'; +import {FirstPhaseResultInputModule} from '../../+first-phase-result-input/first-phase-result-input.module'; +import {SecondPhaseStepsModule} from '../../+results/+second-phase-steps/second-phase-steps.module'; import {TabsModule} from '../tabs.module'; import {SecondPhaseTabComponent} from './second-phase.tab.component'; @@ -10,7 +11,11 @@ const routes: Routes = [{path: '', component: SecondPhaseTabComponent}]; @NgModule({ declarations: [SecondPhaseTabComponent], - imports: [RouterModule.forChild(routes), TabsModule], - providers: [TransportProblemService], + imports: [ + FirstPhaseResultInputModule, + RouterModule.forChild(routes), + SecondPhaseStepsModule, + TabsModule, + ], }) export class SecondPhaseTabModule {} diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.spec.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.spec.ts new file mode 100644 index 0000000..0cc4224 --- /dev/null +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.spec.ts @@ -0,0 +1,85 @@ +import {HttpClientModule} from '@angular/common/http'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import { + resultTableMock, + secondPhaseFirstResultMock, +} from '@opres/shared/data/mocks'; +import {TranslateModule} from '@ngx-translate/core'; +import {of} from 'rxjs'; + +import {Language} from '../../../../components/layout/language-switcher/language-switcher.service'; +import {LoadingHandlerService} from '../../../../services/loading-handler.service'; +import { + SecondPhaseResult, + transportProblemCacheBuster$, + TransportProblemService, +} from '../../transport-problem.service'; + +import {SecondPhaseTabComponent} from './second-phase.tab.component'; +import {SecondPhaseTabModule} from './second-phase.tab.module'; + +const secondPhaseResultMock: SecondPhaseResult = { + steps: secondPhaseFirstResultMock, + epsilon: { + value: 216, + }, +}; + +describe('SecondPhaseTabComponent', () => { + let fixture: ComponentFixture; + let component: SecondPhaseTabComponent; + let transportProblemService: TransportProblemService; + let loadingHandler: LoadingHandlerService; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [SecondPhaseTabComponent], + imports: [ + SecondPhaseTabModule, + HttpClientModule, + TranslateModule.forRoot({defaultLanguage: 'en'}), + ], + }); + + fixture = TestBed.createComponent(SecondPhaseTabComponent); + component = fixture.componentInstance; + transportProblemService = TestBed.inject(TransportProblemService); + loadingHandler = TestBed.inject(LoadingHandlerService); + }); + + it('should be created', () => + expect(component).toBeInstanceOf(SecondPhaseTabComponent)); + + it('should check default values', (done) => { + expect(component.result$.getValue()).toEqual(null); + component.currentLanguage$.subscribe((value: Language) => { + expect(value).toEqual('en'); + done(); + }); + }); + + it('should calculate second phase', () => { + const loadingStartSpy = jest.spyOn(loadingHandler, 'start'); + const loadingStopSpy = jest.spyOn(loadingHandler, 'stop'); + const getSecondPhaseResultSpy = jest + .spyOn(transportProblemService, 'getSecondPhaseResult') + .mockReturnValue(of(secondPhaseResultMock)); + component.onCalculate(resultTableMock); + expect(loadingStartSpy).toHaveBeenCalled(); + expect(getSecondPhaseResultSpy).toHaveBeenCalledWith(resultTableMock); + expect(component.result$.getValue()).toEqual(secondPhaseResultMock); + expect(loadingStopSpy).toHaveBeenCalled(); + }); + + it('should destroy', () => { + const loadingStopSpy = jest.spyOn(loadingHandler, 'stop'); + const transportProblemCacheBusterSpy = jest.spyOn( + transportProblemCacheBuster$, + 'next', + ); + component.ngOnDestroy(); + expect(loadingStopSpy).toHaveBeenCalled(); + expect(transportProblemCacheBusterSpy).toHaveBeenCalled(); + }); +}); diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.template.html b/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.template.html index 3e6d623..22696ac 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.template.html +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/second-phase-tab/second-phase.tab.template.html @@ -1,5 +1,9 @@ -
- - {{'TRANSPORTATION_PROBLEM.INFO' | translate}} - -
+ + + + diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.module.ts b/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.module.ts index e0eeef8..26cd0d4 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.module.ts +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.module.ts @@ -16,9 +16,10 @@ import {TranslateModule} from '@ngx-translate/core'; import {FirstPhaseStepsModule} from '../+results/+first-phase-steps/first-phase-steps.module'; import {TransportTableModule} from '../+table/transport-table.module'; import {TransportProblemPipesModule} from '../pipes/transport-problem-pipes.module'; +import {TransportProblemService} from '../transport-problem.service'; @NgModule({ - providers: [ErrorHandlerService], + providers: [ErrorHandlerService, TransportProblemService], exports: [ CommonModule, InfoCardModule, diff --git a/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.style.scss b/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.style.scss index 1f2ffb9..e28022d 100644 --- a/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.style.scss +++ b/apps/frontend/src/app/pages/transport-problem/+tabs/tabs.style.scss @@ -1,4 +1,4 @@ -@import '../../../styles/variables/colors'; +@import 'libs/shared/styles/colors'; :host { section { diff --git a/apps/frontend/src/app/pages/transport-problem/tests/transport-problem.service.spec.ts b/apps/frontend/src/app/pages/transport-problem/tests/transport-problem.service.spec.ts index cc962e2..b1c12c6 100644 --- a/apps/frontend/src/app/pages/transport-problem/tests/transport-problem.service.spec.ts +++ b/apps/frontend/src/app/pages/transport-problem/tests/transport-problem.service.spec.ts @@ -16,7 +16,6 @@ import { TransportProblemService, } from '../transport-problem.service'; -// FIXME [2022-08-20] after having working second phase implementation modify this describe('TransportProblemService', () => { const epsilonMock: Epsilon = {value: 123}; const epsilonErrorMock = { diff --git a/apps/frontend/src/app/pages/transport-problem/transport-problem.service.ts b/apps/frontend/src/app/pages/transport-problem/transport-problem.service.ts index 45d8ead..053aa7e 100644 --- a/apps/frontend/src/app/pages/transport-problem/transport-problem.service.ts +++ b/apps/frontend/src/app/pages/transport-problem/transport-problem.service.ts @@ -79,7 +79,7 @@ export class TransportProblemService { .post>(url, transportTable, {params}) .pipe( catchError(this.errorHandler.showError), - switchMap((steps) => { + switchMap((steps: Array) => { return this.getEpsilonResult( last(steps)?.transportation || [], mode === 'explanations', diff --git a/apps/frontend/src/assets/i18n/en.json b/apps/frontend/src/assets/i18n/en.json index 637e943..c37dd9c 100644 --- a/apps/frontend/src/assets/i18n/en.json +++ b/apps/frontend/src/assets/i18n/en.json @@ -58,9 +58,9 @@ }, "INVALID_TP_DATA": "The given problem is not solvable! Try another one.", "TABLE_EXPLANATION": "The table in the top left corner represents the costs, next to it on the right it's the stocks of storages and under them can be found the demands of the shops.", - "INFO": "Currently only the first-phase is available, but the second-phase will be released soon as well!", "FIRST_PHASE_RESULT_TITLE": "First phase in {{steps}} steps", - "EXPLANATION_TITLE": "Explanation" + "EXPLANATION_TITLE": "Explanation", + "SECOND_PHASE_RESULT_TITLE": "Second phase in {{steps}} steps" }, "SECOND_PHASE": { "NAME": "Second phase only" @@ -81,7 +81,25 @@ }, "COLUMNS_SELECT": "Shops (columns)", "ROWS_SELECT": "Storages (rows)", - "INFO": "Under construction! It will be available soon." + "SECOND_PHASE_TABLE_EXPLANATIONS": { + "TITLE": "Table explanations", + "SIDE_TABLES": { + "TITLE": "Side tables (below and right side)", + "DESCRIPTION": "These contain auxiliary variables that show which cells are worth taking into the base." + }, + "ORANGE_BADGES": { + "TITLE": "Orange badges (number, plus sign)", + "DESCRIPTION": "Reduced cost values. Positive values are shown by a plus sign." + }, + "ORANGE_NUMBERS_LEFT": { + "TITLE": "Orange numbers (top left corner)", + "DESCRIPTION": "Marks those cells that are part of the circle and marked by order." + }, + "ORANGE_BORDERED_CELL": { + "TITLE": "Orange bordered cell", + "DESCRIPTION": "The cell that is going into the base next." + } + } }, "ASSIGNMENT_PROBLEM": { "TABLE_SIZE_SELECT": "Table size (n*n)", diff --git a/apps/frontend/src/assets/i18n/hu.json b/apps/frontend/src/assets/i18n/hu.json index 3855ef9..d362d34 100644 --- a/apps/frontend/src/assets/i18n/hu.json +++ b/apps/frontend/src/assets/i18n/hu.json @@ -58,9 +58,9 @@ }, "INVALID_TP_DATA": "A megadott probléma nem megoldható! Próbáljon másikat.", "TABLE_EXPLANATION": "A felső bal oldali nagy tábla a költségek, mellette a jobb oldalon levő a raktárkészletek, míg alattuk a bolti igények megadására alkalmas mezők találhatók egymástól elválasztva.", - "INFO": "Jelenleg csak az első fázis elérhető, de a második fázis is hamarosan kiadásra kerül!", "FIRST_PHASE_RESULT_TITLE": "Első fázis {{steps}} lépésben", - "EXPLANATION_TITLE": "Magyarázat" + "EXPLANATION_TITLE": "Magyarázat", + "SECOND_PHASE_RESULT_TITLE": "Második fázis {{steps}} lépésben" }, "SECOND_PHASE": { "NAME": "Csak második fázis" @@ -81,7 +81,25 @@ }, "COLUMNS_SELECT": "Boltok (oszlopok)", "ROWS_SELECT": "Raktárak (sorok)", - "INFO": "Fejlesztés alatt! Hamarosan elérhetővé válik." + "SECOND_PHASE_TABLE_EXPLANATIONS": { + "TITLE": "Tábla magyarázatok", + "SIDE_TABLES": { + "TITLE": "Oldalsó táblák (allul és jobb oldalt)", + "DESCRIPTION": "Azokat a segédváltozókat tartalmazzák, amik megmutatják, hogy a cellákat érdemes-e bevinni a bázisba." + }, + "ORANGE_BADGES": { + "TITLE": "Narancs körök (számmal, plusz jellel)", + "DESCRIPTION": "Redukált költségek értékei. Ahol ennek értéke pozitív oda egy plusz jel került." + }, + "ORANGE_NUMBERS_LEFT": { + "TITLE": "Narancs számok (bal felső sarkok)", + "DESCRIPTION": "A körhöz tartozó cellákat jelölik sorszámozva." + }, + "ORANGE_BORDERED_CELL": { + "TITLE": "Narancs bekeretezett cella", + "DESCRIPTION": "Az a cella, amit beviszünk a bázisba." + } + } }, "ASSIGNMENT_PROBLEM": { "TABLE_SIZE_SELECT": "Tábla méret (n*n)", diff --git a/apps/frontend/src/styles.scss b/apps/frontend/src/styles.scss index 9c10e11..8561a10 100644 --- a/apps/frontend/src/styles.scss +++ b/apps/frontend/src/styles.scss @@ -1,6 +1,6 @@ @use 'sass:map'; @use '@angular/material' as mat; -@import 'apps/frontend/src/app/styles/variables/colors'; +@import 'libs/shared/styles/colors'; @include mat.core(); @@ -47,6 +47,11 @@ body { --primary-text: black; --accent: #{mat.get-color-from-palette($frontend-light-accent, 800)}; + --accent-transparent: #{mat.get-color-from-palette( + $frontend-light-accent, + 800, + 0.16 + )}; --accent-variant: #{mat.get-color-from-palette($frontend-light-accent, 200)}; --warn: #{mat.get-color-from-palette($frontend-light-warn, 900)}; @@ -63,6 +68,11 @@ body { --primary-text: white; --accent: #{mat.get-color-from-palette($frontend-dark-accent, 300)}; + --accent-transparent: #{mat.get-color-from-palette( + $frontend-dark-accent, + 300, + 0.16 + )}; --accent-variant: #{mat.get-color-from-palette($frontend-dark-accent, 600)}; --warn: #{mat.get-color-from-palette($frontend-dark-warn, 200)}; diff --git a/libs/shared/data/mocks/src/lib/transport-problem/transport-problem.mock.ts b/libs/shared/data/mocks/src/lib/transport-problem/transport-problem.mock.ts index 752fb75..6a29e1e 100644 --- a/libs/shared/data/mocks/src/lib/transport-problem/transport-problem.mock.ts +++ b/libs/shared/data/mocks/src/lib/transport-problem/transport-problem.mock.ts @@ -1,4 +1,31 @@ -import {TPData} from '@opres/shared/types'; +import {TPData, TransportTable} from '@opres/shared/types'; + +export const resultTableMock: TransportTable = [ + { + '0': {cost: 8}, + '1': {cost: 7}, + '2': {cost: 3}, + '3': {cost: 2, transported: 15}, + }, + { + '0': {cost: 1, transported: 18}, + '1': {cost: 4}, + '2': {cost: 2, transported: 25}, + '3': {cost: 5}, + }, + { + '0': {cost: 2}, + '1': {cost: 3, transported: 13}, + '2': {cost: 4, transported: 10}, + '3': {cost: 7, transported: 5}, + }, + { + '0': {cost: 1}, + '1': {cost: 1, transported: 19}, + '2': {cost: 4}, + '3': {cost: 4}, + }, +]; export const tpDataFirstMock: TPData = { costs: [ diff --git a/apps/frontend/src/app/styles/variables/colors.scss b/libs/shared/styles/colors.scss similarity index 87% rename from apps/frontend/src/app/styles/variables/colors.scss rename to libs/shared/styles/colors.scss index d576e94..5983a45 100644 --- a/apps/frontend/src/app/styles/variables/colors.scss +++ b/libs/shared/styles/colors.scss @@ -5,6 +5,7 @@ $primary: var(--primary); $primary-variant: var(--primary-variant); $accent: var(--accent); +$accent-transparent: var(--accent-transparent); $accent-variant: var(--accent-variant); $warn: var(--warn); diff --git a/libs/ui/tables/src/lib/input-table/input-table.component.ts b/libs/ui/tables/src/lib/input-table/input-table.component.ts index 1b9902c..62be542 100644 --- a/libs/ui/tables/src/lib/input-table/input-table.component.ts +++ b/libs/ui/tables/src/lib/input-table/input-table.component.ts @@ -19,6 +19,7 @@ import {InputTableService} from './input-table.service'; @Component({ selector: 'opres-input-table[key]', templateUrl: './input-table.template.html', + styleUrls: ['../tables.style.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class InputTableComponent { diff --git a/libs/ui/tables/src/lib/table/table.component.ts b/libs/ui/tables/src/lib/table/table.component.ts index b60a03a..a1b2edf 100644 --- a/libs/ui/tables/src/lib/table/table.component.ts +++ b/libs/ui/tables/src/lib/table/table.component.ts @@ -1,29 +1,21 @@ import {ChangeDetectionStrategy, Component, Input} from '@angular/core'; -import {Table} from '@opres/shared/types'; +import {SelectedCell, Table} from '@opres/shared/types'; import {Observable, of} from 'rxjs'; @Component({ selector: 'opres-table[tableSource]', templateUrl: './table.template.html', - styles: [ - ` - td { - min-width: 50px; - text-align: center; - } - - .hidden-transport { - color: transparent; - } - `, - ], + styleUrls: ['./table.style.scss', '../tables.style.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) export class TableComponent { public tableSource$ = new Observable
(); public badgeSource$ = new Observable
(); + public secondaryBadgeSource$ = new Observable
(); @Input() public showZeros = true; + @Input() public mainMarkedCell?: SelectedCell; + @Input() public markedCells?: Array; @Input() public set tableSource(value: Observable
| Table) { if (Array.isArray(value)) this.tableSource$ = of(value); @@ -34,4 +26,19 @@ export class TableComponent { if (Array.isArray(value)) this.badgeSource$ = of(value); else this.badgeSource$ = value; } + + @Input() public set secondaryBadgeSource(value: Observable
| Table) { + if (Array.isArray(value)) this.secondaryBadgeSource$ = of(value); + else this.secondaryBadgeSource = value; + } + + public getCircleStepNumber( + rowIndex: number, + columnIndex: number, + ): number | undefined { + const index = this.markedCells?.findIndex( + (cell) => cell.x === columnIndex && cell.y === rowIndex, + ); + return index === undefined ? undefined : index + 1; + } } diff --git a/libs/ui/tables/src/lib/table/table.style.scss b/libs/ui/tables/src/lib/table/table.style.scss new file mode 100644 index 0000000..41abcf3 --- /dev/null +++ b/libs/ui/tables/src/lib/table/table.style.scss @@ -0,0 +1,24 @@ +@import 'libs/shared/styles/colors'; + +td { + position: relative; + min-width: 50px; + text-align: center; + + &.main-marked-cell { + outline: 1px solid $accent; + background-color: $accent-transparent; + } + + .marked-cell { + position: absolute; + top: 4px; + left: 8px; + color: $accent; + font-size: 12px; + } +} + +.hidden-transport { + color: transparent; +} diff --git a/libs/ui/tables/src/lib/table/table.template.html b/libs/ui/tables/src/lib/table/table.template.html index 0991df2..e20b061 100644 --- a/libs/ui/tables/src/lib/table/table.template.html +++ b/libs/ui/tables/src/lib/table/table.template.html @@ -1,5 +1,5 @@
-
{{header}} - + - {{element[header] || 0}} - - {{circleStep}} + - {{element[header] || 0}} + {{element[header] || 0}} + {{element[header] || 0}} - + + + {{element[header] || 0}} + + +