Initial commit: Aeroflot Flights Web Angular 12 application
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { FlightModel } from './models-legacy';
|
||||
|
||||
export interface AirlinesReference<T> {
|
||||
Aeroflot: T;
|
||||
Aurora: T;
|
||||
Rossiya: T;
|
||||
Pobeda: T;
|
||||
AirFrance: T;
|
||||
}
|
||||
|
||||
export const AirlineCodes: AirlinesReference<string> = {
|
||||
Aeroflot: 'SU',
|
||||
Aurora: 'HZ',
|
||||
Rossiya: 'FV',
|
||||
Pobeda: 'DP',
|
||||
AirFrance: 'AF',
|
||||
};
|
||||
|
||||
export const airlineCodes = Object.values(AirlineCodes);
|
||||
|
||||
export interface Airline {
|
||||
code: string;
|
||||
url: string;
|
||||
registrationUrl: string;
|
||||
canViewStatus: boolean;
|
||||
canViewNativeStatus: boolean;
|
||||
canRedirect: boolean;
|
||||
canRegister: boolean;
|
||||
}
|
||||
|
||||
export const Airlines: AirlinesReference<Airline> = {
|
||||
Aeroflot: {
|
||||
code: AirlineCodes.Aeroflot,
|
||||
url: 'https://www.aeroflot.ru/sb/ckin/app/ru-ru',
|
||||
registrationUrl: 'https://www.aeroflot.ru/sb/ckin/app/ru-ru',
|
||||
canViewStatus: true,
|
||||
canViewNativeStatus: true,
|
||||
canRedirect: false,
|
||||
canRegister: true,
|
||||
},
|
||||
Aurora: {
|
||||
code: AirlineCodes.Aurora,
|
||||
url: 'https://www.flyaurora.ru',
|
||||
registrationUrl: 'https://www.flyaurora.ru',
|
||||
canViewStatus: true,
|
||||
canViewNativeStatus: false,
|
||||
canRedirect: true,
|
||||
canRegister: true,
|
||||
},
|
||||
Rossiya: {
|
||||
code: AirlineCodes.Rossiya,
|
||||
url: 'https://www.rossiya-airlines.com',
|
||||
registrationUrl: 'https://www.rossiya-airlines.com/flight-with-us/before_flight/the_ways_of_check-in/',
|
||||
canViewStatus: true,
|
||||
canViewNativeStatus: true,
|
||||
canRedirect: false,
|
||||
canRegister: true,
|
||||
},
|
||||
Pobeda: {
|
||||
code: AirlineCodes.Pobeda,
|
||||
url: 'https://www.pobeda.aero',
|
||||
registrationUrl: 'https://www.pobeda.aero',
|
||||
canViewStatus: true,
|
||||
canViewNativeStatus: false,
|
||||
canRedirect: true,
|
||||
canRegister: true,
|
||||
},
|
||||
AirFrance: {
|
||||
code: AirlineCodes.AirFrance,
|
||||
url: '',
|
||||
registrationUrl: '',
|
||||
canViewStatus: false,
|
||||
canViewNativeStatus: false,
|
||||
canRedirect: false,
|
||||
canRegister: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const airlines: Airline[] = Object.values(Airlines);
|
||||
|
||||
export function getAirline(flight?: FlightModel): Airline | undefined {
|
||||
if (!flight) return;
|
||||
const { operatingBy } = flight;
|
||||
const code = operatingBy?.actual || operatingBy?.scheduled;
|
||||
return airlines.find((a) => a.code === code);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Directive } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
@Directive({})
|
||||
export abstract class Destroyable {
|
||||
protected destroy$ = new Subject();
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum DispatchMode {
|
||||
bus = 'Bus',
|
||||
bridge = 'Bridge',
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum FlightIcon {
|
||||
routeChange = 'routeChange',
|
||||
return = 'return',
|
||||
intermediateLanding = 'intermediateLanding',
|
||||
intermediateLandingMulti = 'intermediateLandingMulti',
|
||||
transfer = 'transfer',
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum FlightLoadedState {
|
||||
board,
|
||||
actual,
|
||||
detailed,
|
||||
scheduled,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export enum FlightRequestType {
|
||||
Route = 'route',
|
||||
Arrival = 'arrival',
|
||||
Flight = 'flight',
|
||||
Departure = 'departure',
|
||||
Schedule = 'schedule',
|
||||
Undefined = 'undefined',
|
||||
Details = 'Details'
|
||||
}
|
||||
|
||||
export enum ViewType {
|
||||
Onlineboard = 'onlineboard',
|
||||
Schedule = 'schedule',
|
||||
FlightsMap = 'flights-map',
|
||||
Undefined = 'undefined',
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export enum FlightStatusLegacy {
|
||||
scheduled = 'Scheduled',
|
||||
sent = 'Sent',
|
||||
inFlight = 'InFlight',
|
||||
landed = 'Landed',
|
||||
arrived = 'Arrived',
|
||||
delayed = 'Delayed',
|
||||
cancelled = 'Cancelled',
|
||||
unknown = 'Unknown',
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export * from './flight-icon.enum';
|
||||
export * from './flight-status.enum';
|
||||
export * from './language.enum';
|
||||
export * from './dispatch-mode.enum';
|
||||
export * from './operation-status.enum';
|
||||
export * from './time-type.enum';
|
||||
export * from './flight-loaded-state.enum';
|
||||
export * from './route-type.enum';
|
||||
@@ -0,0 +1,12 @@
|
||||
export enum Language {
|
||||
ru = 'ru',
|
||||
en = 'en',
|
||||
de = 'de',
|
||||
fr = 'fr',
|
||||
es = 'es',
|
||||
it = 'it',
|
||||
cn = 'cn',
|
||||
ko = 'ko',
|
||||
jp = 'jp',
|
||||
zh = 'zh',
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum OperationStatusLegacy {
|
||||
scheduled = 'Scheduled',
|
||||
inProgress = 'InProgress',
|
||||
finished = 'Finished',
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum RouteTypeLegacy {
|
||||
Direct = 'Direct',
|
||||
MultiLeg = 'MultiLeg',
|
||||
Connecting = 'Connecting',
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum TimeType {
|
||||
scheduled = 'Scheduled',
|
||||
estimated = 'Estimated',
|
||||
actual = 'Actual',
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './translate-http-loader.factory';
|
||||
export * from './schedule.factory';
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ScheduleItemModel, FlightModel } from '@shared/models-legacy';
|
||||
import { IRouteContract } from '@shared/models-legacy/contracts';
|
||||
import { createFlights } from '@shared/models-legacy/populate.logic';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export function createScheduleFromRoutes(
|
||||
contracts: IRouteContract[],
|
||||
dateFrom: Date,
|
||||
dateTo: Date,
|
||||
) {
|
||||
const flights = createFlights(contracts);
|
||||
|
||||
return createSchedule(flights, dateFrom, dateTo);
|
||||
}
|
||||
export function createSchedule(
|
||||
flights: FlightModel[],
|
||||
dateFrom: Date,
|
||||
dateTo: Date,
|
||||
): ScheduleItemModel[] {
|
||||
function byDate(flight: FlightModel) {
|
||||
return moment(flight.dateToSearchBy).startOf('day').toDate().getTime();
|
||||
}
|
||||
|
||||
const result = flights.groupBy(byDate).map(({ key, items: flights }) => {
|
||||
return new ScheduleItemModel({
|
||||
date: moment(key).toDate(),
|
||||
flights,
|
||||
});
|
||||
});
|
||||
|
||||
return addMissingDays(result, dateFrom, dateTo);
|
||||
}
|
||||
|
||||
function addMissingDays(
|
||||
items: ScheduleItemModel[],
|
||||
dateFrom: Date,
|
||||
dateTo: Date,
|
||||
): ScheduleItemModel[] {
|
||||
const map = new Map<number, ScheduleItemModel>();
|
||||
for (const item of items) {
|
||||
if (dateFrom <= item.date && item.date <= dateTo)
|
||||
map.set(item.date.getTime(), item);
|
||||
}
|
||||
|
||||
const daysBetween = moment(dateTo).diff(dateFrom, 'day');
|
||||
|
||||
for (let i = 0; i <= daysBetween; i++) {
|
||||
const date = moment(dateFrom).add(i, 'day').toDate();
|
||||
const key = date.getTime();
|
||||
if (map.has(key)) continue;
|
||||
map.set(key, new ScheduleItemModel({ date }));
|
||||
}
|
||||
|
||||
items = [...map.values()];
|
||||
|
||||
function byDate(a: ScheduleItemModel, b: ScheduleItemModel) {
|
||||
if (a.date > b.date) return 1;
|
||||
if (a.date < b.date) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return items.sort(byDate);
|
||||
}
|
||||
|
||||
const ScheduleLegacyFactories = {
|
||||
createScheduleFromRoutes,
|
||||
createSchedule,
|
||||
};
|
||||
|
||||
export default ScheduleLegacyFactories;
|
||||
@@ -0,0 +1,6 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
|
||||
export function TranslateHttpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const DATE_FORMAT = 'DD.MM.YYYY';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './dateHelper';
|
||||
@@ -0,0 +1,33 @@
|
||||
// https://github.com/ai/convert-layout/blob/main/convert.js
|
||||
function convert(keys: string, values: string) {
|
||||
const reverse = {};
|
||||
const full = {};
|
||||
|
||||
for (let keyIndex = keys.length; keyIndex--; ) {
|
||||
full[keys[keyIndex].toUpperCase()] = values[keyIndex].toUpperCase();
|
||||
full[keys[keyIndex]] = values[keyIndex];
|
||||
}
|
||||
|
||||
for (const key in full) {
|
||||
reverse[full[key]] = key;
|
||||
}
|
||||
|
||||
return {
|
||||
fromEn: function (str) {
|
||||
return str.replace(/./g, function (ch) {
|
||||
return full[ch] || ch;
|
||||
});
|
||||
},
|
||||
toEn: function (str) {
|
||||
return str.replace(/./g, function (ch) {
|
||||
return reverse[ch] || ch;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// copied from build of convert-layout
|
||||
export const ruKeyboardLayout = convert(
|
||||
'.exportsfunc"#$&\',/:;<>?@[]^`abdghijklmqvwyz{|}~',
|
||||
'юучзщкеыагтсЭ№;?эб.ЖжБЮ,"хъ:ёфивпршолдьймцняХ/ЪЁ',
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
type IWithName = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export function sortByName<T extends IWithName>(a: T, b: T) {
|
||||
return sortStrings(a.name, b.name);
|
||||
}
|
||||
|
||||
export function sortStrings(a: string, b: string) {
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './shared.module';
|
||||
@@ -0,0 +1,32 @@
|
||||
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { takeUntil, tap } from 'rxjs/operators';
|
||||
import { HttpCancelService } from '../services/http-cancel.service';
|
||||
|
||||
@Injectable()
|
||||
export class AppInterceptor implements HttpInterceptor {
|
||||
constructor(private httpCancelService: HttpCancelService, private router: Router) {}
|
||||
|
||||
intercept<T>(req: HttpRequest<T>, next: HttpHandler): Observable<HttpEvent<T>> {
|
||||
return next.handle(req).pipe(
|
||||
takeUntil(this.httpCancelService.onCancelPendingRequests()),
|
||||
tap({
|
||||
next: () => {},
|
||||
error: (err) => {
|
||||
if (err instanceof HttpErrorResponse) {
|
||||
switch (err.status) {
|
||||
case 404:
|
||||
this.router.navigateByUrl('/error/404');
|
||||
break;
|
||||
case 500:
|
||||
this.router.navigateByUrl('/error');
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
import { LocalizationService, } from '../services';
|
||||
import { LocalisationReadyDirective } from './localisation-ready.directive';
|
||||
import { DictionariesService } from './../../modules/components/page-filters/services/dictionaries-service';
|
||||
|
||||
describe('LocalisationReadyDirective', () => {
|
||||
let locale: LocalizationService;
|
||||
let dicts: DictionariesService;
|
||||
let tempalte: TemplateRef<any>;
|
||||
let view: ViewContainerRef;
|
||||
let directive: LocalisationReadyDirective;
|
||||
beforeEach(() => {
|
||||
dicts = {
|
||||
ready$: Promise.resolve()
|
||||
} as any
|
||||
locale = {
|
||||
ready$: Promise.resolve()
|
||||
} as any;
|
||||
tempalte = {} as any;
|
||||
view = {
|
||||
createEmbeddedView: jasmine.createSpy().withArgs(tempalte).and.callThrough(),
|
||||
} as any;
|
||||
directive = new LocalisationReadyDirective(dicts, locale, tempalte, view);
|
||||
});
|
||||
it('should wait for localisation to be ready and the create view', async () => {
|
||||
await directive.ngOnInit();
|
||||
expect(view.createEmbeddedView).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Directive, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
import { DictionariesService } from '@app/modules/components/page-filters/services/dictionaries-service';
|
||||
import { LocalizationService } from '../services';
|
||||
|
||||
@Directive({
|
||||
selector: '[localisationReady]',
|
||||
})
|
||||
export class LocalisationReadyDirective {
|
||||
constructor(
|
||||
private dictionaries: DictionariesService,
|
||||
private locale: LocalizationService,
|
||||
private templateRef: TemplateRef<any>,
|
||||
private viewContainer: ViewContainerRef,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await Promise.all([this.locale.ready$, this.dictionaries.ready$]);
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export const calendarDe = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
|
||||
monthNamesShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
|
||||
dayNames: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
|
||||
dayNamesShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
|
||||
dayNamesMin: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
|
||||
today: 'Heute',
|
||||
clear: 'Leeren',
|
||||
dateFormat: 'tt.mm.jj',
|
||||
weekHeader: 'Wo',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const calendarEn = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
||||
today: 'Today',
|
||||
clear: 'Clear',
|
||||
dateFormat: 'dd.mm.yy',
|
||||
weekHeader: 'Wk.',
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
export const calendarEs = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: [
|
||||
'Enero',
|
||||
'Febrero',
|
||||
'Marzo',
|
||||
'Abril',
|
||||
'Mayo',
|
||||
'Junio',
|
||||
'Julio',
|
||||
'Agosto',
|
||||
'Septiembre',
|
||||
'Octubre',
|
||||
'Noviembre',
|
||||
'Diciembre',
|
||||
],
|
||||
monthNamesShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
|
||||
dayNames: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
|
||||
dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'],
|
||||
dayNamesMin: ['D', 'L', 'M', 'X', 'J', 'V', 'S'],
|
||||
today: 'Hoy',
|
||||
clear: 'Claro',
|
||||
dateFormat: 'dd/mm/yy',
|
||||
weekHeader: 'Sm.',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const calendarFr = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
|
||||
monthNamesShort: ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'],
|
||||
dayNames: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
|
||||
dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
|
||||
dayNamesMin: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],
|
||||
today: "Aujourd'hui",
|
||||
clear: 'Clair',
|
||||
dateFormat: 'dd.mm.yy',
|
||||
weekHeader: 'Sem.',
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
export const calendarIt = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: [
|
||||
'Gennaio',
|
||||
'Febbraio',
|
||||
'Marzo',
|
||||
'Aprile',
|
||||
'Maggio',
|
||||
'Giugno',
|
||||
'Luglio',
|
||||
'Agosto',
|
||||
'Settembre',
|
||||
'Ottobre',
|
||||
'Novembre',
|
||||
'Dicembre',
|
||||
],
|
||||
monthNamesShort: ['Gen', 'Feb', 'Mar', 'Apr', 'Maggio', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
|
||||
dayNames: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
|
||||
dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
|
||||
dayNamesMin: ['Do', 'Lu', 'Ma', 'Me', 'Gi', 'Ve', 'Sa'],
|
||||
today: 'Oggi',
|
||||
clear: 'Cancella',
|
||||
dateFormat: 'gg.mm.aa',
|
||||
weekHeader: 'sett.',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const calendarJp = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
monthNamesShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
|
||||
dayNames: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日'],
|
||||
dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'],
|
||||
dayNamesMin: ['日', '月', '火', '水', '木', '金', '土'],
|
||||
today: '今日',
|
||||
clear: 'クリア',
|
||||
dateFormat: 'yy.mm.dd',
|
||||
weekHeader: '週',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const calendarKo = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
||||
dayNames: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'],
|
||||
dayNamesShort: ['일', '월', '화', '수', '목', '금', '토'],
|
||||
dayNamesMin: ['일', '월', '화', '수', '목', '금', '토'],
|
||||
today: '오늘',
|
||||
clear: '지우기',
|
||||
dateFormat: '일.월.년',
|
||||
weekHeader: '주',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const calendarRu = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
|
||||
monthNamesShort: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
|
||||
dayNames: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'],
|
||||
dayNamesShort: ['Воск', 'Пон', 'Вт', 'Ср', 'Четв', 'Пят', 'Суб'],
|
||||
dayNamesMin: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
|
||||
today: 'Сегодня',
|
||||
clear: 'Очистить',
|
||||
dateFormat: 'dd.mm.yy',
|
||||
weekHeader: 'Нд.',
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const calendarZh = {
|
||||
firstDayOfWeek: 1,
|
||||
monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
monthNamesShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
dayNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
|
||||
dayNamesShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||
dayNamesMin: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||
today: '今天',
|
||||
clear: '清除',
|
||||
dateFormat: 'yy.mm.dd.',
|
||||
weekHeader: '周',
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './calendar-ru';
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IFlightId } from './flight-id.model';
|
||||
import { OnboardServiceModel } from './onboard-service.model';
|
||||
|
||||
export interface AircraftTypeModel {
|
||||
title: string;
|
||||
name: string;
|
||||
registration: string;
|
||||
type: string;
|
||||
configuration: string;
|
||||
previousFlightId: IFlightId;
|
||||
onBoardServices: OnboardServiceModel[];
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { AircraftTypeModel } from './aircraft-type.model';
|
||||
import { IFlightId } from './flight-id.model';
|
||||
import { Seat } from './seat.model';
|
||||
|
||||
export interface IAircraft {
|
||||
readonly scheduled: IAircraftScheduled;
|
||||
actual: AircraftTypeModel;
|
||||
configuration: IAircraftConfiguration;
|
||||
previousFlight: IFlightId;
|
||||
}
|
||||
|
||||
export interface IAircraftScheduled {
|
||||
readonly title: string;
|
||||
}
|
||||
|
||||
export interface IAircraftConfiguration {
|
||||
seats: Seat[];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface AirportInfoModificationModel {
|
||||
airport: boolean;
|
||||
city: boolean;
|
||||
country: boolean;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import * as moment from 'moment';
|
||||
|
||||
export interface AppSettings {
|
||||
showDebugVersion: boolean;
|
||||
buyPeriod: {
|
||||
min: number;
|
||||
max: number;
|
||||
};
|
||||
refreshInterval: number;
|
||||
flightStatusAvailableFrom: number;
|
||||
boardSearchFrom: number;
|
||||
boardSearchTo: number;
|
||||
scheduleSearchFrom: number;
|
||||
scheduleSearchTo: number;
|
||||
flightsMapSearchFrom: number;
|
||||
flightsMapSearchTo: number;
|
||||
boardMaxDate: Date;
|
||||
boardMinDate: Date;
|
||||
scheduleMaxDate: Date;
|
||||
scheduleMinDate: Date;
|
||||
//intervalId: number | null;
|
||||
//startValidationDatesUpdatingInterval(): void;
|
||||
//stopValidationDatesUpdatingInterval(): void;
|
||||
}
|
||||
export const defaultSettings: AppSettings = {
|
||||
showDebugVersion: false,
|
||||
buyPeriod: {
|
||||
min: 2,
|
||||
max: 330,
|
||||
},
|
||||
refreshInterval: 3 * 60 * 1000, // 3 min
|
||||
// refreshInterval: 5 * 1000, // 5 sec
|
||||
flightStatusAvailableFrom: 2,
|
||||
boardSearchFrom: 1,
|
||||
boardSearchTo: 7,
|
||||
scheduleSearchFrom: 1,
|
||||
scheduleSearchTo: 330,
|
||||
flightsMapSearchFrom: 1,
|
||||
flightsMapSearchTo: 186,
|
||||
boardMaxDate: new Date(),
|
||||
boardMinDate: new Date(),
|
||||
scheduleMaxDate: new Date(),
|
||||
scheduleMinDate: new Date(),
|
||||
//intervalId: null,
|
||||
//startValidationDatesUpdatingInterval() {
|
||||
// always clear previous interval because appSettings instance can be
|
||||
// shared between several components; So calling settings.observeDayChange
|
||||
// from different parts will cause this.intervalId rewrite and other
|
||||
// intervals won't be cleared.
|
||||
// if (this.intervalId) {
|
||||
// this.stopValidationDatesUpdatingInterval();
|
||||
// }
|
||||
|
||||
// this.intervalId = setInterval(() => {
|
||||
// this.boardMaxDate = moment().startOf('day').add(this.boardSearchTo, 'day').toDate();
|
||||
// this.boardMinDate = moment().startOf('day').add(-this.boardSearchFrom, 'day').toDate();
|
||||
// this.scheduleMaxDate = moment().startOf('day').add(this.scheduleSearchTo, 'day').toDate();
|
||||
// this.scheduleMinDate = moment().startOf('day').add(-this.scheduleSearchFrom, 'day').toDate();
|
||||
// this.flightsMapMaxDate = moment().startOf('day').add(this.flightsMapSearchTo, 'day').toDate();
|
||||
// this.flightsMapMinDate = moment().startOf('day').add(-this.flightsMapSearchFrom, 'day').toDate();
|
||||
// }, 60 * 1000);
|
||||
// },
|
||||
// stopValidationDatesUpdatingInterval() {
|
||||
// if (!this.intervalId) {
|
||||
// return; //already cleared
|
||||
// }
|
||||
|
||||
// clearInterval(this.intervalId);
|
||||
// this.intervalId = null;
|
||||
// },
|
||||
};
|
||||
export interface AppSettingsContract {
|
||||
showDebugVersion: string;
|
||||
uiOptions: UIOptionsConfiguration;
|
||||
}
|
||||
|
||||
export interface UIOptionsConfiguration {
|
||||
isTestVersion: string;
|
||||
filter: UIOptionsFilterConfiguration;
|
||||
buttons: UIOptionsButtonsConfiguration;
|
||||
}
|
||||
|
||||
export interface UIOptionsFilterConfiguration {
|
||||
schedule: UIOptionsFilterScheduleConfiguration;
|
||||
onlineboard: UIOptionsFilterOnlineboardConfiguration;
|
||||
}
|
||||
|
||||
export interface UIOptionsFilterScheduleConfiguration {
|
||||
searchFrom: string;
|
||||
searchTo: string;
|
||||
timeStep: string;
|
||||
}
|
||||
|
||||
|
||||
export interface UIOptionsFilterOnlineboardConfiguration {
|
||||
searchFrom: string;
|
||||
searchTo: string;
|
||||
timeStep: string;
|
||||
}
|
||||
|
||||
export interface UIOptionsButtonsConfiguration {
|
||||
buyTicket: UIOptionsButtonsBuyTicketConfiguration;
|
||||
flightStatus: UIOptionsButtonsFlightStatusConfiguration;
|
||||
}
|
||||
|
||||
export interface UIOptionsButtonsBuyTicketConfiguration {
|
||||
period: UIOptionsButtonsBuyTicketPeriodConfiguration;
|
||||
}
|
||||
|
||||
export interface UIOptionsButtonsBuyTicketPeriodConfiguration {
|
||||
max: string;
|
||||
min: string;
|
||||
}
|
||||
|
||||
export interface UIOptionsButtonsFlightStatusConfiguration {
|
||||
visible: string;
|
||||
availableFrom: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { IFlightArrivalStationTimes } from '@typings/flight/flight-station';
|
||||
import { DispatchMode, OperationStatusLegacy } from '../enumerators';
|
||||
import { ArrivalTimesModel } from './arrival-times.model';
|
||||
import { IAirportInfo } from '@typings/airport-info';
|
||||
|
||||
export interface IArrivalStation {
|
||||
scheduled: IAirportInfo;
|
||||
latest: IAirportInfo;
|
||||
bagBelt: string;
|
||||
terminal?: string;
|
||||
deboardingStatus: OperationStatusLegacy;
|
||||
deboardingStatusOriginal?: OperationStatusLegacy;
|
||||
_times: IFlightArrivalStationTimes;
|
||||
times: ArrivalTimesModel;
|
||||
dispatch: DispatchMode;
|
||||
gate: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { PeriodFlightTimeModel } from './period-flight-time.model';
|
||||
import { IFlightTime } from './flight-time.model';
|
||||
import { ITimesModel } from './times.model';
|
||||
|
||||
export interface ArrivalTimesModel extends ITimesModel {
|
||||
estimatedTouchDown: IFlightTime;
|
||||
estimatedBlockOn: IFlightTime;
|
||||
actualTouchDown: IFlightTime;
|
||||
actualBlockOn: IFlightTime;
|
||||
deboarding: PeriodFlightTimeModel;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class IBookingClasses {
|
||||
codes: string[];
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { IConnectingFlight } from './contracts';
|
||||
import { ConnectingFlight } from './flight.model';
|
||||
import { IFlightStrategy } from './flight.strategy';
|
||||
import { RouteTypeLegacy } from '../enumerators';
|
||||
import { createFlights } from './populate.logic';
|
||||
|
||||
export class ConnectingFlightStrategy implements IFlightStrategy {
|
||||
constructor(private getStrategy: (type: RouteTypeLegacy) => IFlightStrategy) {}
|
||||
|
||||
getLegs(flight: ConnectingFlight) {
|
||||
return flight.flights
|
||||
.map((f) => this.getStrategy(f.routeType).getLegs(f))
|
||||
.reduce((allLegs, flightLegs) => allLegs.concat(flightLegs), []);
|
||||
}
|
||||
getFlights(flight: ConnectingFlight) {
|
||||
return [...flight.flights];
|
||||
}
|
||||
|
||||
getFirstLeg(flight: ConnectingFlight) {
|
||||
const first = this.getFirstFlight(flight);
|
||||
return this.getStrategy(first.routeType).getFirstLeg(first);
|
||||
}
|
||||
getCurrentLeg(flight: ConnectingFlight) {
|
||||
const legs = this.getLegs(flight);
|
||||
return legs.find((l) => l.isCurrent) ?? legs[0];
|
||||
}
|
||||
getLastLeg(flight: ConnectingFlight) {
|
||||
const last = this.getLastFlight(flight);
|
||||
return this.getStrategy(last.routeType).getLastLeg(last);
|
||||
}
|
||||
|
||||
getFirstFlight(flight: ConnectingFlight) {
|
||||
return flight.flights[0];
|
||||
}
|
||||
getLastFlight(flight: ConnectingFlight) {
|
||||
return flight.flights[flight.flights.length - 1];
|
||||
}
|
||||
|
||||
getFlightId(flight: ConnectingFlight, isLast: boolean) {
|
||||
const { flights } = flight;
|
||||
|
||||
const single = isLast ? flights[flights.length - 1] : flights[0];
|
||||
|
||||
return this.getStrategy(single.routeType).getFlightId(single);
|
||||
}
|
||||
|
||||
fromContract(contract: IConnectingFlight) {
|
||||
const flights = createFlights(contract.flights);
|
||||
|
||||
const flight: Partial<ConnectingFlight> = {
|
||||
...contract,
|
||||
isMultiFlight: true,
|
||||
isMultiLeg: true,
|
||||
flights,
|
||||
};
|
||||
|
||||
return flight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface IAirportInfoContract {
|
||||
airport: string;
|
||||
airportCode: string;
|
||||
city: string;
|
||||
cityCode: string;
|
||||
country: string;
|
||||
countryCode: string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IFlightTimeContract, IPeriodFlightTimeContract } from './flight-time.contract';
|
||||
import { OperationStatusLegacy } from '../../enumerators';
|
||||
import { IStationContract } from './station.contract';
|
||||
|
||||
export interface IArrivalStationContract extends IStationContract {
|
||||
bagBelt: string;
|
||||
deboardingStatus: OperationStatusLegacy;
|
||||
times: IArrivalTimesContract;
|
||||
}
|
||||
|
||||
export interface IArrivalTimesContract {
|
||||
scheduledArrival: IFlightTimeContract;
|
||||
estimatedTouchDown: IFlightTimeContract;
|
||||
estimatedBlockOn: IFlightTimeContract;
|
||||
actualTouchDown: IFlightTimeContract;
|
||||
actualBlockOn: IFlightTimeContract;
|
||||
deboarding: IPeriodFlightTimeContract;
|
||||
|
||||
actualTime: IFlightTimeContract;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { OperationStatusLegacy } from '../../enumerators';
|
||||
import { IFlightTimeContract, IPeriodFlightTimeContract } from './flight-time.contract';
|
||||
import { IStationContract } from './station.contract';
|
||||
|
||||
export interface IDepartureStationContract extends IStationContract {
|
||||
checkinStatus: string;
|
||||
boardingStatus: OperationStatusLegacy;
|
||||
times: IDepartureTimesContract;
|
||||
boardingStatusOriginal: string;
|
||||
}
|
||||
|
||||
export interface IDepartureTimesContract {
|
||||
scheduledDeparture: IFlightTimeContract;
|
||||
estimatedBlockOff: IFlightTimeContract;
|
||||
estimatedTakeOff: IFlightTimeContract;
|
||||
actualBlockOff: IFlightTimeContract;
|
||||
actualTakeOff: IFlightTimeContract;
|
||||
checkIn: IPeriodFlightTimeContract;
|
||||
boarding: IPeriodFlightTimeContract;
|
||||
actualTime: IFlightTimeContract;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { DayChangeModel } from '../day-change.model';
|
||||
|
||||
export interface IFlightTimeContract {
|
||||
utc: Date;
|
||||
local: Date;
|
||||
localTime: string;
|
||||
tzOffset: number;
|
||||
dayChange: DayChangeModel;
|
||||
unix: number;
|
||||
}
|
||||
|
||||
export interface IPeriodFlightTimeContract {
|
||||
start: IFlightTimeContract;
|
||||
end: IFlightTimeContract;
|
||||
}
|
||||
|
||||
export interface IUnixUtcDateContract {
|
||||
utc: Date;
|
||||
unix: number;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { IOperatingByContract } from './operator.contract';
|
||||
|
||||
export interface IFlightContract {
|
||||
operatingBy: IOperatingByContract;
|
||||
flightId?: IFlightContractId;
|
||||
pId: string;
|
||||
}
|
||||
|
||||
export interface IFlightContractId {
|
||||
carrier: string;
|
||||
flightNumber: string;
|
||||
suffix: string;
|
||||
date: Date;
|
||||
localDate?: Date
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from './airport-info.contract';
|
||||
export * from './arrival-station.contract';
|
||||
export * from './departure-station.contract';
|
||||
export * from './flight-time.contract';
|
||||
export * from './flight.contract';
|
||||
export * from './leg.contract';
|
||||
export * from './operator.contract';
|
||||
export * from './route.contract';
|
||||
export * from './station.contract';
|
||||
@@ -0,0 +1,26 @@
|
||||
import { FlightStatusLegacy } from '../../enumerators';
|
||||
import { IArrivalStationContract } from './arrival-station.contract';
|
||||
import { IDepartureStationContract } from './departure-station.contract';
|
||||
|
||||
import { IDaysOfWeek } from '../days-of-week.model';
|
||||
import { EquipmentModel } from '../equipment.model';
|
||||
import { PassengersModel } from '../passengers.model';
|
||||
import { TrafficRestrictionsModel } from '../traffic-restrictions.model';
|
||||
|
||||
type ILegContractFlags = {
|
||||
checkinAvailable: boolean;
|
||||
returnToAirport: boolean;
|
||||
routeChanged: boolean;
|
||||
};
|
||||
|
||||
export interface ILegContract {
|
||||
updated: Date;
|
||||
status: FlightStatusLegacy;
|
||||
departure: IDepartureStationContract;
|
||||
arrival: IArrivalStationContract;
|
||||
daysOfWeek: IDaysOfWeek;
|
||||
equipment: EquipmentModel;
|
||||
passengers: PassengersModel;
|
||||
trafficRestrictions: TrafficRestrictionsModel;
|
||||
flags: ILegContractFlags;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface IOperatorContract {
|
||||
carrier: string;
|
||||
flightNumber: string;
|
||||
}
|
||||
|
||||
export interface IOperatingByContract {
|
||||
scheduled: string;
|
||||
actual: string;
|
||||
operators: IOperatorContract[];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IFlightContract } from './flight.contract';
|
||||
import { ILegContract } from './leg.contract';
|
||||
import { FlightStatusLegacy, RouteTypeLegacy } from '../../enumerators/';
|
||||
|
||||
export interface IRouteContract extends IFlightContract {
|
||||
routeType: RouteTypeLegacy;
|
||||
status: FlightStatusLegacy;
|
||||
}
|
||||
|
||||
export interface IDirectFlight extends IRouteContract {
|
||||
leg: ILegContract;
|
||||
}
|
||||
|
||||
export interface IMultiFlight extends IRouteContract {
|
||||
legs: ILegContract[];
|
||||
}
|
||||
|
||||
export interface IConnectingFlight extends IRouteContract {
|
||||
flights: (IDirectFlight | IMultiFlight)[];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { DispatchMode } from '../../enumerators';
|
||||
import { IAirportInfoContract } from './airport-info.contract';
|
||||
import { ITransition } from '../transition.model';
|
||||
|
||||
export interface IStationContract {
|
||||
scheduled: IAirportInfoContract;
|
||||
latest: IAirportInfoContract;
|
||||
terminal: string;
|
||||
gate: string;
|
||||
parkingStand: string;
|
||||
dispatch: DispatchMode;
|
||||
transition: ITransition;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DayChangeModel {
|
||||
value: number;
|
||||
title: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface IDaysOfWeek {
|
||||
current: string;
|
||||
flight: string;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { IAirportInfo } from '@typings/airport-info';
|
||||
import { IFlightDepartureStationTimes } from '@typings/flight/flight-station';
|
||||
import { DispatchMode, OperationStatusLegacy } from '../enumerators';
|
||||
import { DepartureTimesModel } from './departure-times.model';
|
||||
|
||||
export interface IDepartureStation {
|
||||
scheduled: IAirportInfo;
|
||||
checkinStatus: string;
|
||||
_times: IFlightDepartureStationTimes;
|
||||
times: DepartureTimesModel;
|
||||
latest?: IAirportInfo;
|
||||
gate: string;
|
||||
terminal?: string;
|
||||
parkingStand: string;
|
||||
dispatch: DispatchMode;
|
||||
boardingStatus: OperationStatusLegacy;
|
||||
boardingStatusOriginal: OperationStatusLegacy;
|
||||
checkinStatusOriginal?: OperationStatusLegacy;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IFlightTime } from './flight-time.model';
|
||||
import { PeriodFlightTimeModel } from './period-flight-time.model';
|
||||
import { ITimesModel } from './times.model';
|
||||
|
||||
export interface DepartureTimesModel extends ITimesModel {
|
||||
estimatedTakeOff: IFlightTime;
|
||||
estimatedBlockOff: IFlightTime;
|
||||
actualTakeOff: IFlightTime;
|
||||
actualBlockOff: IFlightTime;
|
||||
boarding: PeriodFlightTimeModel;
|
||||
checkIn: PeriodFlightTimeModel;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { IFlightFactory } from '.';
|
||||
import { IDirectFlight } from './contracts';
|
||||
import { DirectFlight } from './flight.model';
|
||||
import { IFlightStrategy } from './flight.strategy';
|
||||
|
||||
export class DirectFlightStrategy implements IFlightStrategy {
|
||||
getLegs(flight: DirectFlight) {
|
||||
return [flight.leg];
|
||||
}
|
||||
getFlights(flight: DirectFlight) {
|
||||
return [flight];
|
||||
}
|
||||
|
||||
getFirstLeg(flight: DirectFlight) {
|
||||
return flight.leg;
|
||||
}
|
||||
getCurrentLeg(flight: DirectFlight) {
|
||||
return flight.leg;
|
||||
}
|
||||
getLastLeg(flight: DirectFlight) {
|
||||
return flight.leg;
|
||||
}
|
||||
|
||||
getFirstFlight(flight: DirectFlight) {
|
||||
return flight;
|
||||
}
|
||||
getLastFlight(flight: DirectFlight) {
|
||||
return flight;
|
||||
}
|
||||
|
||||
getFlightId(flight: DirectFlight) {
|
||||
return flight.flightId;
|
||||
}
|
||||
|
||||
fromContract(contract: IDirectFlight, { createLeg }: IFlightFactory) {
|
||||
const leg = createLeg(contract.leg);
|
||||
|
||||
const flight: Partial<DirectFlight> = {
|
||||
...contract,
|
||||
leg,
|
||||
};
|
||||
|
||||
return flight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Duration, getDuration, getDurationDiff } from './duration.model';
|
||||
import * as moment from 'moment';
|
||||
|
||||
describe('DurationMode', () => {
|
||||
it('should handle empty values', () => {
|
||||
expect(getDuration(new Date(), undefined)).toBeUndefined();
|
||||
expect(getDuration(undefined, new Date())).toBeUndefined();
|
||||
expect(getDuration(undefined, undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return duration from dates', () => {
|
||||
const from = moment();
|
||||
const to = from.clone();
|
||||
to.add(5, 'hour').add(12, 'minute');
|
||||
|
||||
const actual = getDuration(from.toDate(), to.toDate());
|
||||
const expected: Duration = { days: 0, hours: 5, minutes: 12 };
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return duration from dates with more than 1 day', () => {
|
||||
const from = moment();
|
||||
const to = from.clone();
|
||||
to.add(5, 'hour').add(12, 'minute').add(2, 'day');
|
||||
|
||||
const actual = getDuration(from.toDate(), to.toDate());
|
||||
const expected: Duration = { days: 2, hours: 5, minutes: 12 };
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should return duration from dates in string', () => {
|
||||
const from = moment();
|
||||
const to = from.clone();
|
||||
to.add(5, 'hour').add(12, 'minute');
|
||||
|
||||
const actual = getDuration(from.toDate().toISOString(), to.toDate().toISOString());
|
||||
const expected: Duration = { days: 0, hours: 5, minutes: 12 };
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should calculate diff between two durations', () => {
|
||||
const first = { days: 1, hours: 2, minutes: 4 } as Duration;
|
||||
const next = { days: 2, hours: 5, minutes: 12 } as Duration;
|
||||
|
||||
const actual = getDurationDiff(first, next);
|
||||
const expected: Duration = { days: 1, hours: 3, minutes: 8 };
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { PeriodFlightTimeModel } from './period-flight-time.model';
|
||||
|
||||
export interface Duration {
|
||||
days: number;
|
||||
hours: number;
|
||||
minutes: number;
|
||||
}
|
||||
|
||||
export const zeroDuration: Duration = { days: 0, hours: 0, minutes: 0 };
|
||||
|
||||
export function getPeriodDuration(period?: PeriodFlightTimeModel) {
|
||||
return getDuration(period?.start?.local, period?.end?.local) || zeroDuration;
|
||||
}
|
||||
|
||||
export function getDuration(from?: Date | string, to?: Date | string): Duration | undefined {
|
||||
if (!from || !to) return undefined;
|
||||
return getDurationMs(new Date(to).getTime() - new Date(from).getTime());
|
||||
}
|
||||
|
||||
export function getTotalMinutes({ days = 0, hours = 0, minutes = 0 }: Duration = {} as any) {
|
||||
return minutes + hours * 60 + days * 24 * 60;
|
||||
}
|
||||
|
||||
export function getDurationMs(ms: number): Duration {
|
||||
ms = ms / 1000 / 60; // ms / milliseconds / seconds
|
||||
const days = Math.floor(ms / (60 * 24));
|
||||
const hours = Math.floor((ms / 60) % 24);
|
||||
const minutes = Math.floor(ms % 60);
|
||||
return { days, hours, minutes: minutes };
|
||||
}
|
||||
|
||||
export function getDurationDiff(first: Duration, next: Duration): Duration {
|
||||
if (!first || !next) return zeroDuration;
|
||||
|
||||
const totalMinutes = getTotalMinutes(next) - getTotalMinutes(first);
|
||||
return getDurationMs(totalMinutes * 60 * 1000);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { IAircraft } from './aircraft.model';
|
||||
import { IServiceType } from './service-type.model';
|
||||
import { ServiceClassModel } from './service-class.model';
|
||||
import { Meal } from './meal.model';
|
||||
import { IBookingClasses } from './booking-classes.model';
|
||||
|
||||
export class EquipmentModel {
|
||||
serviceType: IServiceType;
|
||||
aircraft: IAircraft;
|
||||
serviceClasses: ServiceClassModel[];
|
||||
bookingClasses: IBookingClasses;
|
||||
meal: Meal[];
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IRouteContract } from './contracts';
|
||||
import { IFlightStrategy } from './flight.strategy';
|
||||
import { RouteModel } from './route.model';
|
||||
|
||||
export class FallbackFlightStrategy implements IFlightStrategy {
|
||||
getLegs(flight: RouteModel) {
|
||||
return [];
|
||||
}
|
||||
getFlights(flight: RouteModel) {
|
||||
return [];
|
||||
}
|
||||
|
||||
getFirstLeg(flight: RouteModel) {
|
||||
return {} as any;
|
||||
}
|
||||
getCurrentLeg(flight: RouteModel) {
|
||||
return {} as any;
|
||||
}
|
||||
getLastLeg(flight: RouteModel) {
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
getFirstFlight(flight: RouteModel) {
|
||||
return {} as any;
|
||||
}
|
||||
getLastFlight(flight: RouteModel) {
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
getFlightId(flight: RouteModel) {
|
||||
return {} as any;
|
||||
}
|
||||
|
||||
fromContract(contract: IRouteContract) {
|
||||
return {} as any;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface IFlightId {
|
||||
carrier: string;
|
||||
flightNumber: string;
|
||||
suffix: string;
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface IPrevFlight extends IFlightId {
|
||||
localDate?: Date;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { getFirstLeg, getLastLeg, getTotalMinutes, IFlightTime, Leg } from '.';
|
||||
import { FlightModel } from './flight.model';
|
||||
|
||||
type CompareDirection = 1 | -1;
|
||||
|
||||
// all covered with tests as a part of test for sort-flights-by.pipe.spec.ts
|
||||
export function compareByArrivalDesc(a: FlightModel, b: FlightModel) {
|
||||
return compareByArrival(a, b, -1);
|
||||
}
|
||||
export function compareByDepartureDesc(a: FlightModel, b: FlightModel) {
|
||||
return compareByDeparture(a, b, -1);
|
||||
}
|
||||
export function compareByDurationDesc(a: FlightModel, b: FlightModel) {
|
||||
return compareByDuration(a, b, -1);
|
||||
}
|
||||
|
||||
export function compareByArrivalAsc(a: FlightModel, b: FlightModel) {
|
||||
return compareByArrival(a, b, 1);
|
||||
}
|
||||
export function compareByDepartureAsc(a: FlightModel, b: FlightModel) {
|
||||
return compareByDeparture(a, b, 1);
|
||||
}
|
||||
export function compareByDurationAsc(a: FlightModel, b: FlightModel) {
|
||||
return compareByDuration(a, b, 1);
|
||||
}
|
||||
|
||||
function compareByArrival(a: FlightModel, b: FlightModel, direction: CompareDirection) {
|
||||
return compareByLeg(getLastLeg(a), getLastLeg(b), 'arrival', direction);
|
||||
}
|
||||
|
||||
function compareByDeparture(a: FlightModel, b: FlightModel, direction: CompareDirection) {
|
||||
return compareByLeg(getFirstLeg(a), getFirstLeg(b), 'departure', direction);
|
||||
}
|
||||
|
||||
function compareByDuration(a: FlightModel, b: FlightModel, direction: CompareDirection) {
|
||||
const amin = getTotalMinutes(a.duration);
|
||||
const bmin = getTotalMinutes(b.duration);
|
||||
if (amin > bmin) return direction;
|
||||
if (amin < bmin) return direction * -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function compareByLeg(a: Leg, b: Leg, property: keyof Pick<Leg, 'arrival' | 'departure'>, direction: CompareDirection) {
|
||||
return compareByTime(a[property].times.scheduled, b[property].times.scheduled, direction);
|
||||
}
|
||||
function compareByTime(a: IFlightTime, b: IFlightTime, direction: CompareDirection) {
|
||||
if (a.utc > b.utc) return direction;
|
||||
if (a.utc < b.utc) return direction * -1;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { DayChangeModel } from './day-change.model';
|
||||
|
||||
export interface IFlightTime {
|
||||
utc: Date;
|
||||
local: Date;
|
||||
localTime: string;
|
||||
tzOffset: number;
|
||||
dayChange: DayChangeModel;
|
||||
unix: number;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class FlightUpdateSignalModel {
|
||||
flightNumber: string;
|
||||
carrier: string;
|
||||
date: Date;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { FlightLoadedState } from '../enumerators';
|
||||
import { IFlightId } from './flight-id.model';
|
||||
import { Leg } from './leg.model';
|
||||
import { IOperatingByLegacy } from './operator.model';
|
||||
import { RouteModel } from './route.model';
|
||||
|
||||
export interface FlightModel extends RouteModel {
|
||||
operatingBy: IOperatingByLegacy;
|
||||
flightId: IFlightId;
|
||||
dateToSearchBy: Date;
|
||||
id: string;
|
||||
boardings: number;
|
||||
isMultiFlight: boolean; // aka Connected
|
||||
isMultiLeg: boolean; // aka Multi-Segment, Multi-Leg
|
||||
hash: string;
|
||||
previous: FlightModel;
|
||||
next: FlightModel;
|
||||
loadedState: FlightLoadedState;
|
||||
lastUpdate: Date;
|
||||
flights?: FlightModel[];
|
||||
leg: Leg;
|
||||
legs: Leg[];
|
||||
}
|
||||
|
||||
export interface DirectFlight extends FlightModel {
|
||||
leg: Leg;
|
||||
}
|
||||
|
||||
export interface MultiLegFlight extends FlightModel {
|
||||
legs: Leg[];
|
||||
}
|
||||
|
||||
export interface ConnectingFlight extends FlightModel {
|
||||
flights: FlightModel[];
|
||||
}
|
||||
|
||||
export type AnyOfFlightModel = DirectFlight | MultiLegFlight | ConnectingFlight;
|
||||
|
||||
export type ListItem<T> = T & { expanded?: boolean; visible?: boolean };
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ILegContract, IRouteContract } from './contracts';
|
||||
import { IFlightId } from './flight-id.model';
|
||||
import { FlightModel } from './flight.model';
|
||||
import { Leg } from './leg.model';
|
||||
import { RouteModel } from './route.model';
|
||||
|
||||
export interface IFlightFactory {
|
||||
createFlight: (contract: IRouteContract) => FlightModel;
|
||||
createLeg: (contract: ILegContract) => Leg;
|
||||
}
|
||||
export interface IFlightStrategy {
|
||||
getLegs(flight: RouteModel): Leg[];
|
||||
getFlights(flight: RouteModel): FlightModel[];
|
||||
getFirstLeg(flight: RouteModel): Leg;
|
||||
getCurrentLeg(flight: RouteModel): Leg;
|
||||
getLastLeg(flight: RouteModel): Leg;
|
||||
getFirstFlight(flight: RouteModel): FlightModel;
|
||||
getLastFlight(flight: RouteModel): FlightModel;
|
||||
getFlightId(flight: RouteModel, isLast?: boolean): IFlightId;
|
||||
fromContract: (contract: IRouteContract, factory: IFlightFactory) => Partial<FlightModel>;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { ConnectingFlightStrategy } from './connected-flight.strategy';
|
||||
import { DirectFlightStrategy } from './direct-flight.strategy';
|
||||
import { MultiLegFlightStrategy } from './multileg-fligh.strategy';
|
||||
import { IFlightStrategy } from './flight.strategy';
|
||||
import { RouteTypeLegacy } from '../enumerators';
|
||||
import { RouteModel } from './route.model';
|
||||
import { FallbackFlightStrategy } from './fallback-flight.strategy';
|
||||
import { FlightModel, IArrivalStation, IDepartureStation } from '.';
|
||||
|
||||
const direct = new DirectFlightStrategy();
|
||||
const connecting = new ConnectingFlightStrategy(getStrategyByType);
|
||||
const multiLeg = new MultiLegFlightStrategy();
|
||||
const fallback = new FallbackFlightStrategy();
|
||||
|
||||
export function getStrategy(flight: RouteModel): IFlightStrategy {
|
||||
return getStrategyByType(flight?.routeType);
|
||||
}
|
||||
|
||||
export function getStrategyByType(type?: RouteTypeLegacy): IFlightStrategy {
|
||||
switch (type) {
|
||||
case RouteTypeLegacy.Connecting:
|
||||
return connecting;
|
||||
case RouteTypeLegacy.Direct:
|
||||
return direct;
|
||||
case RouteTypeLegacy.MultiLeg:
|
||||
return multiLeg;
|
||||
default:
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
export function getArrival(
|
||||
flight: FlightModel,
|
||||
property: keyof Pick<IArrivalStation, 'scheduled' | 'latest'> = 'scheduled',
|
||||
) {
|
||||
return getLastLeg(flight).arrival[property]?.cityCode;
|
||||
}
|
||||
export function getDeparture(
|
||||
flight: FlightModel,
|
||||
property: keyof Pick<
|
||||
IDepartureStation,
|
||||
'scheduled' | 'latest'
|
||||
> = 'scheduled',
|
||||
) {
|
||||
return getFirstLeg(flight).departure[property]?.cityCode;
|
||||
}
|
||||
export function getAllFlights(flights: RouteModel[]) {
|
||||
return (flights = flights
|
||||
.map((f) => getFlights(f))
|
||||
.reduce((a, c) => a.concat(c)));
|
||||
}
|
||||
export function getAllLegs(flights: RouteModel[]) {
|
||||
return flights.reduce((a, c) => a.concat(getLegs(c)), []);
|
||||
}
|
||||
export function getLegs(flight: RouteModel) {
|
||||
return getStrategy(flight).getLegs(flight);
|
||||
}
|
||||
export function getFlights(flight: RouteModel) {
|
||||
return getStrategy(flight).getFlights(flight);
|
||||
}
|
||||
export function getFirstLeg(flight: RouteModel) {
|
||||
return getStrategy(flight).getFirstLeg(flight);
|
||||
}
|
||||
export function getCurrentLeg(flight: RouteModel) {
|
||||
return getStrategy(flight).getCurrentLeg(flight);
|
||||
}
|
||||
export function getLastLeg(flight: RouteModel) {
|
||||
return getStrategy(flight).getLastLeg(flight);
|
||||
}
|
||||
export function getFirstFlight(flight: RouteModel) {
|
||||
return getStrategy(flight).getFirstFlight(flight);
|
||||
}
|
||||
export function getLastFlight(flight: RouteModel) {
|
||||
return getStrategy(flight).getLastFlight(flight);
|
||||
}
|
||||
export function getFlightId(flight: RouteModel) {
|
||||
return getStrategy(flight).getFlightId(flight);
|
||||
}
|
||||
|
||||
const DeprecatedFlightUtils = {
|
||||
getFirstLeg,
|
||||
};
|
||||
|
||||
export default DeprecatedFlightUtils;
|
||||
@@ -0,0 +1,38 @@
|
||||
export * from './times.model';
|
||||
export * from './station.model';
|
||||
export * from './route.model';
|
||||
export * from './flight.model';
|
||||
export * from './flight-id.model';
|
||||
export * from './leg.model';
|
||||
export * from './departure-station.model';
|
||||
export * from './flight-time.model';
|
||||
export * from './departure-times.model';
|
||||
export * from './arrival-times.model';
|
||||
export * from './period-flight-time.model';
|
||||
export * from './arrival-station.model';
|
||||
export * from './days-of-week.model';
|
||||
export * from './passenger-info.model';
|
||||
export * from './passengers.model';
|
||||
export * from './equipment.model';
|
||||
export * from './aircraft.model';
|
||||
export * from './aircraft-type.model';
|
||||
export * from './onboard-service.model';
|
||||
export * from './traffic-restrictions.model';
|
||||
export * from './service-type.model';
|
||||
export * from './service-class.model';
|
||||
export * from './booking-classes.model';
|
||||
export * from './operator.model';
|
||||
export * from './airport-info-modification.model';
|
||||
export * from './duration.model';
|
||||
export * from './schedule-item.model';
|
||||
export * from './day-change.model';
|
||||
export * from './flight-update-signal.model';
|
||||
export * from './app-settings.model';
|
||||
export * from './seat.model';
|
||||
export * from './transition.model';
|
||||
export * from './flight.strategy';
|
||||
export * from './get-flight.strategy';
|
||||
export * from './transfer.model';
|
||||
export * from './transfer.logic';
|
||||
export * from './sort-by.enum';
|
||||
export * from './flight-sorting.logic';
|
||||
@@ -0,0 +1,37 @@
|
||||
import { FlightStatusLegacy } from '../enumerators';
|
||||
import { PassengersModel } from './passengers.model';
|
||||
import { EquipmentModel } from './equipment.model';
|
||||
import { TrafficRestrictionsModel } from './traffic-restrictions.model';
|
||||
import { Duration } from './duration.model';
|
||||
import { IDepartureStation } from './departure-station.model';
|
||||
import { IArrivalStation } from './arrival-station.model';
|
||||
import { IDaysOfWeek } from './days-of-week.model';
|
||||
import { IOperatingByLegacy, ITransition } from '.';
|
||||
|
||||
export interface Leg {
|
||||
departure: IDepartureStation;
|
||||
arrival: IArrivalStation;
|
||||
daysOfWeek: IDaysOfWeek;
|
||||
daysForTabs?: string[];
|
||||
index: number;
|
||||
status: FlightStatusLegacy;
|
||||
passengers: PassengersModel;
|
||||
equipment: EquipmentModel;
|
||||
trafficRestrictions: TrafficRestrictionsModel;
|
||||
reroute: boolean;
|
||||
changeRoute: boolean;
|
||||
updated: Date;
|
||||
isCurrent: boolean; // TODO: KS: create task to add this on backend
|
||||
previous: Leg;
|
||||
next: Leg;
|
||||
flightPercent: number;
|
||||
remainingFlightDuration: Duration;
|
||||
checkInPercent: number;
|
||||
duration: Duration; // current flight time/duration
|
||||
scheduledDuration: Duration;
|
||||
estimatedDuration: Duration;
|
||||
crossIndex: number;
|
||||
operatingBy: IOperatingByLegacy;
|
||||
hasCodesharing: boolean;
|
||||
transition: ITransition;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface Meal {
|
||||
type: MealType;
|
||||
}
|
||||
|
||||
export enum MealType {
|
||||
Economy = 'Economy',
|
||||
Comfort = 'Comfort',
|
||||
Business = 'Business',
|
||||
Special = 'Special',
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { IMultiFlight } from './contracts';
|
||||
import { MultiLegFlight } from './flight.model';
|
||||
import { IFlightFactory, IFlightStrategy } from './flight.strategy';
|
||||
export class MultiLegFlightStrategy implements IFlightStrategy {
|
||||
getLegs(flight: MultiLegFlight) {
|
||||
return flight.legs;
|
||||
}
|
||||
getFlights(flight: MultiLegFlight) {
|
||||
return [flight];
|
||||
}
|
||||
|
||||
getFirstLeg(flight: MultiLegFlight) {
|
||||
return flight.legs[0];
|
||||
}
|
||||
getCurrentLeg(flight: MultiLegFlight) {
|
||||
return flight.legs.find((l) => l.isCurrent) ?? flight.legs[0];
|
||||
}
|
||||
getLastLeg(flight: MultiLegFlight) {
|
||||
return flight.legs[flight.legs.length - 1];
|
||||
}
|
||||
|
||||
getFirstFlight(flight: MultiLegFlight) {
|
||||
return flight;
|
||||
}
|
||||
getLastFlight(flight: MultiLegFlight) {
|
||||
return flight;
|
||||
}
|
||||
|
||||
getFlightId(flight: MultiLegFlight) {
|
||||
return flight.flightId;
|
||||
}
|
||||
|
||||
fromContract(contract: IMultiFlight, { createLeg }: IFlightFactory) {
|
||||
const legs = contract.legs?.map((t) => createLeg(t)) ?? [];
|
||||
|
||||
const flight: Partial<MultiLegFlight> = {
|
||||
...contract,
|
||||
legs,
|
||||
isMultiLeg: true,
|
||||
};
|
||||
|
||||
return flight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class OnboardServiceModel {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export class OperatorModel {
|
||||
carrier: string;
|
||||
flightNumber: string;
|
||||
}
|
||||
|
||||
export interface IOperatingByLegacy {
|
||||
scheduled: string;
|
||||
actual: string;
|
||||
operators: OperatorModel[];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export class PassengerInfoModel {
|
||||
code: string;
|
||||
count: number;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { PassengerInfoModel } from './passenger-info.model';
|
||||
|
||||
export class PassengersModel {
|
||||
booked: PassengerInfoModel;
|
||||
actual: PassengerInfoModel;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { IFlightTime } from './flight-time.model';
|
||||
|
||||
export class PeriodFlightTimeModel {
|
||||
start: IFlightTime;
|
||||
end: IFlightTime;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import * as moment from 'moment';
|
||||
import { ILegContract } from './contracts';
|
||||
import { calculateLegState, calculateRemainingDuration, createLeg } from './populate.logic';
|
||||
|
||||
describe('PopulateLogic', () => {
|
||||
beforeEach(() => {
|
||||
jasmine.clock().install().mockDate(new Date(2021, 11, 26, 12, 35, 30));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.clock().uninstall();
|
||||
});
|
||||
const _7h30minBack = () =>
|
||||
moment()
|
||||
.add(-(7 * 60 + 30), 'minutes')
|
||||
.toDate();
|
||||
const _8h40minBack = () =>
|
||||
moment()
|
||||
.add(-(8 * 60 + 40), 'minutes')
|
||||
.toDate();
|
||||
const _1hBack = () =>
|
||||
moment()
|
||||
.add(-(1 * 60), 'minutes')
|
||||
.toDate();
|
||||
const _5h10minForth = () =>
|
||||
moment()
|
||||
.add(5 * 60 + 10, 'minutes')
|
||||
.toDate();
|
||||
const _6h20minForth = () =>
|
||||
moment()
|
||||
.add(6 * 60 + 20, 'minutes')
|
||||
.toDate();
|
||||
const _1hForth = () =>
|
||||
moment()
|
||||
.add(1 * 60, 'minutes')
|
||||
.toDate();
|
||||
|
||||
describe('calculateRemainingDuration', () => {
|
||||
it('should calculate remaining duration using estimated arrival time when both scheduled and estimated given', () => {
|
||||
const legContract = {
|
||||
departure: {
|
||||
times: {
|
||||
actualBlockOff: { utc: _7h30minBack() },
|
||||
scheduledDeparture: { utc: _8h40minBack() },
|
||||
estimatedBlockOff: { utc: _8h40minBack() },
|
||||
},
|
||||
},
|
||||
arrival: { times: { scheduledArrival: { utc: _6h20minForth() }, estimatedBlockOn: { utc: _5h10minForth() } } },
|
||||
status: 'InFlight',
|
||||
} as any as ILegContract;
|
||||
|
||||
const leg = createLeg(legContract);
|
||||
|
||||
expect(calculateRemainingDuration(leg)).toEqual({ days: 0, hours: 5, minutes: 10 });
|
||||
});
|
||||
|
||||
it('should calculate remaining duration using scheduled arrival time when no estimated time given', () => {
|
||||
const legContract = {
|
||||
departure: {
|
||||
times: {
|
||||
actualBlockOff: { utc: _7h30minBack() },
|
||||
scheduledDeparture: { utc: _8h40minBack() },
|
||||
estimatedBlockOff: { utc: _8h40minBack() },
|
||||
},
|
||||
},
|
||||
arrival: { times: { scheduledArrival: { utc: _6h20minForth() } } },
|
||||
status: 'InFlight',
|
||||
} as any as ILegContract;
|
||||
|
||||
const leg = createLeg(legContract);
|
||||
|
||||
expect(calculateRemainingDuration(leg)).toEqual({ days: 0, hours: 6, minutes: 20 });
|
||||
});
|
||||
|
||||
it('should use zero as remaining for already passed', () => {
|
||||
const leg = {
|
||||
departure: { times: { actual: { utc: _5h10minForth() } } },
|
||||
arrival: { times: { scheduled: { utc: _6h20minForth() } } },
|
||||
} as any;
|
||||
expect(calculateRemainingDuration(leg)).toEqual({ days: 0, hours: 0, minutes: 0 });
|
||||
|
||||
const leg2 = {
|
||||
departure: { times: { actual: { utc: _8h40minBack() } } },
|
||||
arrival: { times: { scheduled: { utc: _7h30minBack() } } },
|
||||
} as any;
|
||||
expect(calculateRemainingDuration(leg2)).toEqual({ days: 0, hours: 0, minutes: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateFlightTimePercent', () => {
|
||||
it('should calculate percent using estimated arrival time if both estimated and scheduled times are given', () => {
|
||||
const legContract = {
|
||||
departure: {
|
||||
times: {
|
||||
scheduledDeparture: { utc: _8h40minBack() },
|
||||
actualBlockOff: { utc: _1hBack() },
|
||||
},
|
||||
},
|
||||
arrival: { times: { scheduledArrival: { utc: _6h20minForth() }, estimatedBlockOn: { utc: _1hForth() } } },
|
||||
status: 'InFlight',
|
||||
} as any as ILegContract;
|
||||
|
||||
const leg = createLeg(legContract);
|
||||
calculateLegState(leg);
|
||||
|
||||
expect(leg.flightPercent).toEqual(50);
|
||||
});
|
||||
|
||||
it('should have flight percent eq 0 for future flights', () => {
|
||||
const legContract = {
|
||||
departure: { times: { scheduledDeparture: { utc: _5h10minForth() } } },
|
||||
arrival: { times: { scheduledArrival: { utc: _6h20minForth() } } },
|
||||
} as any as ILegContract;
|
||||
|
||||
const leg = createLeg(legContract);
|
||||
calculateLegState(leg);
|
||||
|
||||
expect(leg.flightPercent).toEqual(0);
|
||||
});
|
||||
|
||||
it('should have flight percent eq 100% for completed flights', () => {
|
||||
const legContract = {
|
||||
departure: { times: { scheduledDeparture: { utc: _8h40minBack() }, actualBlockOff: { utc: _8h40minBack() } } },
|
||||
arrival: { times: { scheduledArrival: { utc: _7h30minBack() }, actualTouchDown: { utc: _7h30minBack() } } },
|
||||
} as any as ILegContract;
|
||||
|
||||
const leg = createLeg(legContract);
|
||||
calculateLegState(leg);
|
||||
|
||||
expect(leg.flightPercent).toEqual(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,313 @@
|
||||
import { FlightLoadedState, FlightStatusLegacy } from '../enumerators';
|
||||
import { IArrivalStation } from './arrival-station.model';
|
||||
import { ILegContract, IMultiFlight, IRouteContract } from './contracts';
|
||||
import { IDepartureStation } from './departure-station.model';
|
||||
import { FlightModel, MultiLegFlight } from './flight.model';
|
||||
import { getStrategy, getStrategyByType } from './get-flight.strategy';
|
||||
import { Leg } from './leg.model';
|
||||
import { Duration } from './duration.model';
|
||||
import { getDuration, zeroDuration } from './duration.model';
|
||||
import * as moment from 'moment';
|
||||
import { getTotalMinutes } from '.';
|
||||
import { MultiLegFlightStrategy } from './multileg-fligh.strategy';
|
||||
|
||||
export function createFlights(contracts: IRouteContract[]): FlightModel[] {
|
||||
return contracts?.map((contract) => createFlight(contract)) ?? [];
|
||||
}
|
||||
|
||||
const factories = { createFlight, createLeg };
|
||||
|
||||
export function createFlight(contract: IRouteContract): FlightModel {
|
||||
const strategy = getStrategyByType(contract.routeType);
|
||||
|
||||
if (contract.routeType == 'MultiLeg') {
|
||||
(contract as IMultiFlight).legs = (contract as IMultiFlight).legs.sort((l1, l2) => {
|
||||
if (!l1.departure.times.actualBlockOff || !l2.departure.times.actualBlockOff) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (l1.departure.times.actualBlockOff.utc > l2.departure.times.actualBlockOff.utc) {
|
||||
return 1;
|
||||
}
|
||||
if (l1.departure.times.actualBlockOff.utc < l2.departure.times.actualBlockOff.utc) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
const flight = strategy.fromContract(contract, factories) as FlightModel;
|
||||
|
||||
return populate(flight);
|
||||
}
|
||||
|
||||
function getSearchDate(leg: Leg) {
|
||||
const departure = leg.departure.times.scheduled.local;
|
||||
return moment.parseZone(departure).local(true).toDate();
|
||||
}
|
||||
|
||||
export function populate(flight: FlightModel) {
|
||||
const strategy = getStrategy(flight);
|
||||
const flights = strategy.getFlights(flight).filter((f) => !!f.flightId);
|
||||
const firstLeg = strategy.getFirstLeg(flight);
|
||||
const lastLeg = strategy.getLastLeg(flight);
|
||||
const legs = strategy.getLegs(flight);
|
||||
|
||||
flight.dateToSearchBy = getSearchDate(firstLeg);
|
||||
flight.lastUpdate = new Date();
|
||||
flight.loadedState = FlightLoadedState.scheduled;
|
||||
flight.boardings = legs.length - 1;
|
||||
|
||||
flight.hash = flights
|
||||
.map(
|
||||
(f) =>
|
||||
`${f.flightId.carrier}${f.flightId.flightNumber}${moment(
|
||||
f.dateToSearchBy,
|
||||
).format('DDMMYYYY')}`,
|
||||
)
|
||||
.join('');
|
||||
|
||||
flight.duration = getScheduledFlightTime(firstLeg, lastLeg);
|
||||
|
||||
link(legs);
|
||||
link(flights);
|
||||
|
||||
legs.forEach((leg, index) => {
|
||||
leg.index = index;
|
||||
leg.crossIndex = index + 1;
|
||||
});
|
||||
|
||||
// debug info, don't remove before 2.01.2022
|
||||
// const operator = 'CI';
|
||||
// flight.operatingBy = {
|
||||
// scheduled: operator,
|
||||
// actual: operator,
|
||||
// operators: [{
|
||||
// carrier: operator,
|
||||
// flightNumber: 'SU-1234',
|
||||
// }]
|
||||
// };
|
||||
return flight;
|
||||
}
|
||||
|
||||
// TODO: refactor this method by moving calculations to server as much as possible
|
||||
export function createLeg(legContract: ILegContract): Leg {
|
||||
const leg: Leg = { ...legContract } as any as Leg;
|
||||
|
||||
leg.arrival = createArrival(legContract);
|
||||
leg.departure = createDeparture(legContract);
|
||||
|
||||
// TODO: TASK 7352 - pending migration to server
|
||||
leg.duration = getCurrentFlightTime(leg);
|
||||
leg.scheduledDuration = getDuration(
|
||||
leg.departure.times.scheduled?.utc,
|
||||
leg.arrival.times.scheduled?.utc,
|
||||
);
|
||||
leg.estimatedDuration = getDuration(
|
||||
leg.departure.times.latest?.utc,
|
||||
leg.arrival.times.latest?.utc,
|
||||
);
|
||||
leg.reroute = legContract.flags?.returnToAirport;
|
||||
leg.changeRoute = legContract.flags?.routeChanged;
|
||||
|
||||
// debug info. could be removed after 02.2022
|
||||
// leg.operatingBy = {
|
||||
// scheduled: 'KL',
|
||||
// actual: 'SU',
|
||||
// operators: [
|
||||
// {
|
||||
// carrier: 'KL',
|
||||
// flightNumber: '1234',
|
||||
// },
|
||||
// {
|
||||
// carrier: 'KL',
|
||||
// flightNumber: '1234',
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
|
||||
// ToDo: Refactor / move to server
|
||||
leg.hasCodesharing = leg.operatingBy?.operators?.length > 0;
|
||||
|
||||
calculateLegState(leg);
|
||||
// debug info. could be removed after 02.2022
|
||||
// leg.reroute = true;
|
||||
// leg.changeRoute = true;
|
||||
|
||||
return leg;
|
||||
}
|
||||
|
||||
export function createDeparture(legContract: ILegContract): IDepartureStation {
|
||||
const contract = legContract.departure;
|
||||
const { times } = contract;
|
||||
|
||||
const departure: IDepartureStation = {
|
||||
boardingStatus: contract.boardingStatus,
|
||||
boardingStatusOriginal: contract.boardingStatus,
|
||||
checkinStatus: contract.checkinStatus,
|
||||
dispatch: contract.dispatch,
|
||||
gate: contract.gate,
|
||||
parkingStand: contract.parkingStand,
|
||||
scheduled: { ...contract.scheduled },
|
||||
terminal: contract.terminal,
|
||||
_times: times as any,
|
||||
times: {
|
||||
...times,
|
||||
scheduled: times.scheduledDeparture,
|
||||
actual:
|
||||
times.actualTime || times.actualBlockOff || times.actualTakeOff,
|
||||
estimated: times.estimatedBlockOff || times.estimatedTakeOff,
|
||||
},
|
||||
latest: contract.latest
|
||||
? { ...contract.latest }
|
||||
: { ...contract.scheduled },
|
||||
// debug info for registration button. could be removed after 02.2022
|
||||
// transition: { registration: { status: OperationStatus.inProgress }} as any
|
||||
};
|
||||
|
||||
departure.times.latest =
|
||||
departure.times.actual || departure.times.estimated;
|
||||
|
||||
// dont remove, this is debug. could be removed after 02.2022
|
||||
// departure.times.scheduled = { ...departure.times.scheduled, utc: new Date(), localTime: '10:35' };
|
||||
// departure.times.scheduled.dayChange = { value: 2, title: '+2' };
|
||||
return departure;
|
||||
}
|
||||
|
||||
export function createArrival(legContract: ILegContract): IArrivalStation {
|
||||
const contract = legContract.arrival;
|
||||
const { times } = contract;
|
||||
|
||||
const arrival: IArrivalStation = {
|
||||
bagBelt: contract.bagBelt,
|
||||
deboardingStatus: contract.deboardingStatus,
|
||||
latest: contract.latest
|
||||
? { ...contract.latest }
|
||||
: { ...contract.scheduled },
|
||||
scheduled: { ...contract.scheduled },
|
||||
terminal: contract.terminal,
|
||||
_times: times as any,
|
||||
times: {
|
||||
...times,
|
||||
scheduled: times.scheduledArrival,
|
||||
estimated: times.estimatedBlockOn || times.estimatedTouchDown,
|
||||
actual:
|
||||
times.actualTime ||
|
||||
times.actualBlockOn ||
|
||||
times.actualTouchDown,
|
||||
},
|
||||
dispatch: contract.dispatch,
|
||||
gate: contract.gate,
|
||||
};
|
||||
|
||||
arrival.times.latest = arrival.times.actual || arrival.times.estimated;
|
||||
|
||||
// dont remove, this is debug. could be removed after 02.2022
|
||||
// arrival.times.latest = { ...arrival.times.latest, utc: moment().add(1, 'hour').toDate(), localTime: '12:25' };
|
||||
// arrival.times.scheduled = { ...arrival.times.scheduled, utc: new Date(), localTime: '10:35' };
|
||||
// arrival.times.scheduled.dayChange = { value: 2, title: '+2' };
|
||||
return arrival;
|
||||
}
|
||||
|
||||
function link<T extends { next: T; previous: T }>(items: T[]) {
|
||||
let previous: T;
|
||||
items.forEach((item) => {
|
||||
item.previous = previous;
|
||||
if (previous) previous.next = item;
|
||||
previous = item;
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentFlightTime(leg: Leg) {
|
||||
const departureTime = leg.departure.times.actual;
|
||||
const arrivalTime = leg.arrival.times.latest?.utc;
|
||||
const now = moment().utc().toDate();
|
||||
|
||||
if (!departureTime) {
|
||||
return zeroDuration;
|
||||
}
|
||||
|
||||
const endTime = arrivalTime <= now ? arrivalTime : now;
|
||||
|
||||
return getDuration(departureTime.utc, endTime);
|
||||
}
|
||||
|
||||
function getScheduledFlightTime(from: Leg, to?: Leg) {
|
||||
to = to ?? from;
|
||||
|
||||
return getDuration(
|
||||
from.departure.times.scheduled?.utc,
|
||||
to.arrival.times.scheduled?.utc,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: TASK 7352 - pending migration to server
|
||||
export function calculateLegState(leg: Leg) {
|
||||
switch (leg.status) {
|
||||
case FlightStatusLegacy.scheduled:
|
||||
case FlightStatusLegacy.cancelled:
|
||||
case FlightStatusLegacy.delayed:
|
||||
case FlightStatusLegacy.unknown:
|
||||
leg.flightPercent = 0;
|
||||
break;
|
||||
case FlightStatusLegacy.arrived:
|
||||
case FlightStatusLegacy.landed:
|
||||
leg.flightPercent = 100;
|
||||
break;
|
||||
case FlightStatusLegacy.sent:
|
||||
leg.flightPercent = 10; // WI 7601
|
||||
break;
|
||||
default:
|
||||
leg.flightPercent = calculateFlightTimePercent(leg);
|
||||
break;
|
||||
}
|
||||
|
||||
leg.remainingFlightDuration = calculateRemainingDuration(leg);
|
||||
|
||||
//debug info
|
||||
// leg.status = FlightStatus.inFlight;
|
||||
// leg.flightPercent = 30;
|
||||
// leg.remainingFlightDuration = { hours: 5, days: 1, minutes: 24 };
|
||||
// leg.checkInDuration = { hours: 4, days: 0, minutes: 12 };
|
||||
}
|
||||
|
||||
export function calculateFlightTimePercent({
|
||||
duration,
|
||||
estimatedDuration,
|
||||
scheduledDuration,
|
||||
}: Leg): number {
|
||||
const totalDuration = estimatedDuration ?? scheduledDuration;
|
||||
|
||||
if (!duration || !totalDuration) return 0;
|
||||
|
||||
const minutesInFlight = getTotalMinutes(duration);
|
||||
const totalMinutes = getTotalMinutes(totalDuration);
|
||||
|
||||
const percentage = Math.round(100 * (minutesInFlight / totalMinutes));
|
||||
|
||||
return Math.min(100, percentage);
|
||||
}
|
||||
|
||||
export function calculateRemainingDuration({
|
||||
departure,
|
||||
arrival,
|
||||
}: Leg): Duration {
|
||||
const start = departure?.times?.actual;
|
||||
const end = arrival?.times?.latest || arrival?.times?.scheduled;
|
||||
|
||||
if (!start || !end) return zeroDuration;
|
||||
|
||||
const from = moment(start.utc);
|
||||
const to = moment(end.utc);
|
||||
const now = moment().utc();
|
||||
|
||||
if (from <= now && now <= to) return getDuration(now.toDate(), to.toDate());
|
||||
|
||||
return zeroDuration;
|
||||
}
|
||||
|
||||
const PopulateLogicLegacy = {
|
||||
createFlights,
|
||||
};
|
||||
|
||||
export default PopulateLogicLegacy;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { RouteTypeLegacy } from '../enumerators/route-type.enum';
|
||||
import { Duration } from './duration.model';
|
||||
import { FlightStatusLegacy } from '../enumerators';
|
||||
|
||||
export interface RouteModel {
|
||||
routeType: RouteTypeLegacy;
|
||||
status: FlightStatusLegacy;
|
||||
duration: Duration;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { FlightModel } from '.';
|
||||
import { ListItem } from './flight.model';
|
||||
|
||||
export class ScheduleItemModel {
|
||||
date: Date;
|
||||
get month() {
|
||||
return this.date.getMonth();
|
||||
}
|
||||
get year() {
|
||||
return this.date.getFullYear();
|
||||
}
|
||||
get dayOfWeek() {
|
||||
return this.date.getDay();
|
||||
}
|
||||
get day() {
|
||||
return this.date.getDate();
|
||||
}
|
||||
|
||||
expanded = false;
|
||||
flights: ListItem<FlightModel>[] = [];
|
||||
constructor(patch: Partial<ScheduleItemModel>) {
|
||||
Object.assign(this, patch);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface Seat {
|
||||
type: SeatType;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export enum SeatType {
|
||||
Economy = 'Economy',
|
||||
Comfort = 'Comfort',
|
||||
Business = 'Business',
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class ServiceClassModel {
|
||||
code: string;
|
||||
mealType: string;
|
||||
canBookPremium: boolean;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface IServiceType {
|
||||
code: string;
|
||||
application: string;
|
||||
operatonType: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum ScheduleSortMode {
|
||||
departureUp = 'departureUp',
|
||||
departureDown = 'departureDown',
|
||||
timeUp = 'timeUp',
|
||||
timeDown = 'timeDown',
|
||||
arrivalUp = 'arrivalUp',
|
||||
arrivalDown = 'arrivalDown',
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { IAirportInfo } from '@typings/airport-info';
|
||||
|
||||
export interface IStation {
|
||||
scheduled: IAirportInfo;
|
||||
latest?: IAirportInfo;
|
||||
terminal?: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IFlightTime } from './flight-time.model';
|
||||
|
||||
export interface ITimesModel {
|
||||
scheduled: IFlightTime;
|
||||
estimated?: IFlightTime;
|
||||
actual?: IFlightTime;
|
||||
latest?: IFlightTime;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class TrafficRestrictionsModel {
|
||||
codes: string;
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { RouteTypeLegacy } from '@app/shared/enumerators';
|
||||
import {
|
||||
FlightModel,
|
||||
getFirstLeg,
|
||||
IArrivalStation,
|
||||
IDepartureStation,
|
||||
Leg,
|
||||
StationChange,
|
||||
Stations,
|
||||
Transfer,
|
||||
} from '@app/shared/models-legacy';
|
||||
import { getLastLeg } from './get-flight.strategy';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TransferLogic {
|
||||
getTransfer(
|
||||
flight: FlightModel,
|
||||
nextFlight: FlightModel,
|
||||
leg: Leg,
|
||||
): Transfer {
|
||||
const type = this.getType(flight, nextFlight, leg);
|
||||
|
||||
if (!type) {
|
||||
return {
|
||||
type: undefined,
|
||||
arrival: undefined,
|
||||
departure: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const { arrival, departure } = this.getStations(
|
||||
type,
|
||||
flight,
|
||||
nextFlight,
|
||||
leg,
|
||||
);
|
||||
|
||||
const stationChange = this.getStationChange({ arrival, departure });
|
||||
|
||||
return {
|
||||
type,
|
||||
arrival,
|
||||
departure,
|
||||
stationChange,
|
||||
};
|
||||
}
|
||||
|
||||
getType(
|
||||
flight: FlightModel,
|
||||
nextFlight: FlightModel,
|
||||
leg: Leg,
|
||||
): RouteTypeLegacy | null {
|
||||
return flight && nextFlight
|
||||
? RouteTypeLegacy.Connecting
|
||||
: leg
|
||||
? RouteTypeLegacy.MultiLeg
|
||||
: null;
|
||||
}
|
||||
|
||||
getStations(
|
||||
type: RouteTypeLegacy,
|
||||
flight: FlightModel,
|
||||
nextFlight: FlightModel,
|
||||
leg: Leg,
|
||||
): Stations {
|
||||
const arrival = this.getArrival(type, flight, leg);
|
||||
const departure = this.getDeparture(type, nextFlight, leg);
|
||||
|
||||
return { arrival, departure };
|
||||
}
|
||||
|
||||
cityChanged({ arrival, departure }: Stations) {
|
||||
const arrivalInfo = arrival?.latest || arrival?.scheduled;
|
||||
const departureInfo = departure?.latest || departure?.scheduled;
|
||||
|
||||
return arrivalInfo?.cityCode !== departureInfo?.cityCode;
|
||||
}
|
||||
|
||||
airportChanged({ arrival, departure }: Stations) {
|
||||
const arrivalInfo = arrival?.latest || arrival?.scheduled;
|
||||
const departureInfo = departure?.latest || departure?.scheduled;
|
||||
|
||||
return arrivalInfo?.airportCode !== departureInfo?.airportCode;
|
||||
}
|
||||
|
||||
terminalChanged({ arrival, departure }: Stations) {
|
||||
if (!arrival?.terminal || !departure?.terminal) return false;
|
||||
|
||||
return arrival.terminal !== departure.terminal;
|
||||
}
|
||||
|
||||
getStationChange(stations: Stations): StationChange {
|
||||
if (this.cityChanged(stations)) {
|
||||
return 'city';
|
||||
}
|
||||
|
||||
if (this.airportChanged(stations)) {
|
||||
return 'airport';
|
||||
}
|
||||
|
||||
if (this.terminalChanged(stations)) {
|
||||
return 'terminal';
|
||||
}
|
||||
|
||||
return 'noChange';
|
||||
}
|
||||
|
||||
private mapToTransfer(
|
||||
arrival: IArrivalStation,
|
||||
departure: IDepartureStation,
|
||||
) {
|
||||
if (this.cityChanged({ arrival, departure })) {
|
||||
return this.constructTransfer(
|
||||
RouteTypeLegacy.Connecting,
|
||||
arrival,
|
||||
departure,
|
||||
'city',
|
||||
);
|
||||
}
|
||||
|
||||
if (this.airportChanged({ arrival, departure })) {
|
||||
return this.constructTransfer(
|
||||
RouteTypeLegacy.Connecting,
|
||||
arrival,
|
||||
departure,
|
||||
'airport',
|
||||
);
|
||||
}
|
||||
|
||||
if (this.terminalChanged({ arrival, departure })) {
|
||||
return this.constructTransfer(
|
||||
RouteTypeLegacy.Connecting,
|
||||
arrival,
|
||||
departure,
|
||||
'terminal',
|
||||
);
|
||||
}
|
||||
|
||||
return this.constructTransfer(
|
||||
RouteTypeLegacy.Connecting,
|
||||
arrival,
|
||||
departure,
|
||||
'noChange',
|
||||
);
|
||||
}
|
||||
|
||||
private getConnectingTransfers(flights: FlightModel[]): Transfer[] {
|
||||
return flights
|
||||
.reduce((acc, cv) => {
|
||||
if (cv.leg) {
|
||||
acc.push(cv.leg);
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (cv.legs) {
|
||||
return [...acc, ...cv.legs];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [])
|
||||
.filter((f) => f.next)
|
||||
.map((leg) => {
|
||||
const { arrival } = leg;
|
||||
const { departure } = leg.next;
|
||||
|
||||
return this.mapToTransfer(arrival, departure);
|
||||
});
|
||||
}
|
||||
|
||||
private getMultiLegTransfers(flights: FlightModel[]) {
|
||||
const connectedLegs = flights
|
||||
.map((flight) => {
|
||||
return (flight.legs || []).filter((l) => l.next);
|
||||
})
|
||||
.reduce((a, b) => a.concat(b), []);
|
||||
|
||||
return connectedLegs.map((leg) => {
|
||||
const { arrival } = leg;
|
||||
const { departure } = leg.next;
|
||||
|
||||
return this.mapToTransfer(arrival, departure);
|
||||
});
|
||||
}
|
||||
|
||||
getTransfers(
|
||||
flights: FlightModel[],
|
||||
type: RouteTypeLegacy.Connecting | RouteTypeLegacy.MultiLeg,
|
||||
): Transfer[] {
|
||||
return type === RouteTypeLegacy.Connecting
|
||||
? this.getConnectingTransfers(flights)
|
||||
: this.getMultiLegTransfers(flights);
|
||||
}
|
||||
|
||||
getPrimaryTransfer(transfers: Transfer[]): Transfer | null {
|
||||
if (!transfers) return null;
|
||||
|
||||
return this.sortTransfers(transfers)[0];
|
||||
}
|
||||
|
||||
private getArrival(
|
||||
type: RouteTypeLegacy,
|
||||
flight: FlightModel,
|
||||
leg: Leg,
|
||||
): IArrivalStation | null {
|
||||
if (!type) return null;
|
||||
|
||||
return type === RouteTypeLegacy.Connecting
|
||||
? getLastLeg(flight).arrival
|
||||
: leg.arrival;
|
||||
}
|
||||
|
||||
private getDeparture(
|
||||
type: RouteTypeLegacy,
|
||||
nextFlight: FlightModel,
|
||||
leg: Leg,
|
||||
): IDepartureStation | null {
|
||||
if (!type) return null;
|
||||
|
||||
return type === RouteTypeLegacy.Connecting
|
||||
? getFirstLeg(nextFlight).departure
|
||||
: leg.next.departure;
|
||||
}
|
||||
|
||||
private constructTransfer(
|
||||
type: RouteTypeLegacy,
|
||||
arrival: IArrivalStation,
|
||||
departure: IDepartureStation,
|
||||
stationChange: StationChange,
|
||||
): Transfer {
|
||||
return {
|
||||
type,
|
||||
arrival,
|
||||
departure,
|
||||
stationChange,
|
||||
};
|
||||
}
|
||||
|
||||
private sortTransfers(transfers: Transfer[]): Transfer[] {
|
||||
const importanceAsc = ['noChange', 'terminal', 'airport'];
|
||||
|
||||
return transfers.sort(
|
||||
(t1, t2) =>
|
||||
importanceAsc.indexOf(t1.stationChange) -
|
||||
importanceAsc.indexOf(t2.stationChange),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { RouteTypeLegacy } from '../enumerators';
|
||||
import { IArrivalStation } from './arrival-station.model';
|
||||
import { IDepartureStation } from './departure-station.model';
|
||||
|
||||
export type StationChange = 'city' | 'airport' | 'terminal' | 'noChange';
|
||||
|
||||
export interface Stations {
|
||||
arrival: IArrivalStation;
|
||||
departure: IDepartureStation;
|
||||
}
|
||||
|
||||
export interface Transfer {
|
||||
type: RouteTypeLegacy;
|
||||
arrival: IArrivalStation;
|
||||
departure: IDepartureStation;
|
||||
stationChange?: StationChange;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { IFlightTime } from '@shared/models-legacy/flight-time.model';
|
||||
|
||||
export interface ITransition {
|
||||
registration: ITransitionItem;
|
||||
boarding: ITransitionItem;
|
||||
deboarding: ITransitionItem;
|
||||
}
|
||||
|
||||
export interface ITransitionItem {
|
||||
status: string;
|
||||
start: IFlightTime;
|
||||
end: IFlightTime;
|
||||
isActual: boolean;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { IArrivalStation } from '../models-legacy';
|
||||
import { AirportTerminalPipe } from './airport-terminal.pipe';
|
||||
|
||||
describe('AirportTerminalPipe', () => {
|
||||
let pipe: AirportTerminalPipe;
|
||||
|
||||
const arrival: IArrivalStation = {
|
||||
latest: {
|
||||
airport: 'Москва',
|
||||
},
|
||||
scheduled: {
|
||||
airport: 'Санкт-Петербург',
|
||||
},
|
||||
terminal: '2C',
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
pipe = new AirportTerminalPipe();
|
||||
});
|
||||
|
||||
it('should return empty string on empty input or missing properties', () => {
|
||||
expect(pipe.transform(null, null)).toBe('');
|
||||
expect(pipe.transform(undefined, undefined)).toBe('');
|
||||
expect(pipe.transform({} as any as IArrivalStation, null)).toBe('');
|
||||
});
|
||||
|
||||
it('should return valid airport with proper formatting with dash', () => {
|
||||
expect(pipe.transform(arrival, 'dash')).toBe('Москва - 2C');
|
||||
});
|
||||
|
||||
it('should return valid airport with proper formatting with brackets', () => {
|
||||
expect(pipe.transform(arrival, 'brackets')).toBe('Москва (2C)');
|
||||
});
|
||||
|
||||
it('should return valid airport with proper formatting with dash when no latest airport given', () => {
|
||||
const airportInfo = {
|
||||
scheduled: {
|
||||
airport: 'Санкт-Петербург',
|
||||
},
|
||||
terminal: '2C',
|
||||
};
|
||||
|
||||
expect(pipe.transform(airportInfo as any as IArrivalStation, 'dash')).toBe('Санкт-Петербург - 2C');
|
||||
});
|
||||
|
||||
it('should return valid airport with proper formatting with brackets when no latest airport given', () => {
|
||||
const airportInfo = {
|
||||
scheduled: {
|
||||
airport: 'Санкт-Петербург',
|
||||
},
|
||||
terminal: '2C',
|
||||
} as IArrivalStation;
|
||||
|
||||
expect(pipe.transform(airportInfo, 'brackets')).toBe('Санкт-Петербург (2C)');
|
||||
});
|
||||
|
||||
it('should return only airport if no terminal given', () => {
|
||||
const testCases = [
|
||||
{
|
||||
scheduled: {
|
||||
airport: 'Санкт-Петербург',
|
||||
},
|
||||
} as IArrivalStation,
|
||||
{
|
||||
scheduled: {
|
||||
airport: 'Санкт-Петербург',
|
||||
},
|
||||
terminal: null,
|
||||
} as IArrivalStation,
|
||||
{
|
||||
scheduled: {
|
||||
airport: 'Санкт-Петербург',
|
||||
},
|
||||
terminal: undefined,
|
||||
} as IArrivalStation,
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
expect(pipe.transform(testCase, 'dash')).toBe('Санкт-Петербург');
|
||||
expect(pipe.transform(testCase, 'brackets')).toBe('Санкт-Петербург');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return only terminal without formatting if onlyTerminal set to true', () => {
|
||||
const station = {
|
||||
scheduled: {
|
||||
airport: 'Санкт-Петербург',
|
||||
},
|
||||
terminal: 'Пулково-1',
|
||||
} as IArrivalStation;
|
||||
|
||||
expect(pipe.transform(station, 'dash', true)).toBe('Пулково-1');
|
||||
expect(pipe.transform(station, 'brackets', true)).toBe('Пулково-1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { IStation } from '../models-legacy';
|
||||
|
||||
@Pipe({
|
||||
name: 'airportTerminal',
|
||||
})
|
||||
export class AirportTerminalPipe implements PipeTransform {
|
||||
transform(
|
||||
model: IStation,
|
||||
separator: 'dash' | 'brackets' = 'dash',
|
||||
onlyTerminal = false,
|
||||
oldValue = false,
|
||||
): string {
|
||||
if (!model?.latest && !model?.scheduled) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const targetAirport = oldValue
|
||||
? model.scheduled
|
||||
: model.latest ?? model.scheduled;
|
||||
|
||||
if (!model.terminal) {
|
||||
return `${targetAirport.airport}`;
|
||||
}
|
||||
|
||||
if (onlyTerminal) {
|
||||
return `${model.terminal}`;
|
||||
}
|
||||
|
||||
return separator === 'dash'
|
||||
? `${targetAirport.airport} - ${model.terminal}`
|
||||
: `${targetAirport.airport} (${model.terminal})`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Leg, OperatorModel } from '../models-legacy';
|
||||
import { CodesharingPipe } from './codesharing.pipe';
|
||||
|
||||
describe('CodesharingPipe', () => {
|
||||
let pipe: CodesharingPipe;
|
||||
|
||||
beforeEach(() => {
|
||||
pipe = new CodesharingPipe();
|
||||
});
|
||||
|
||||
it('should return proper codeshare operators with correct formatting', () => {
|
||||
const testCases = [
|
||||
{
|
||||
legs: undefined,
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
legs: null,
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
legs: [{ departure: {} } as Leg, { arrival: {} } as Leg] as Leg[],
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
legs: [{ departure: {}, operatingBy: {} } as Leg, { arrival: {} } as Leg] as Leg[],
|
||||
expected: '',
|
||||
},
|
||||
{
|
||||
legs: [
|
||||
{ operatingBy: { operators: [{ carrier: 'AM', flightNumber: '1234' }] as OperatorModel[] } } as Leg,
|
||||
{ arrival: {} } as Leg,
|
||||
] as Leg[],
|
||||
expected: 'AM 1234',
|
||||
},
|
||||
{
|
||||
legs: [
|
||||
{
|
||||
operatingBy: {
|
||||
operators: [
|
||||
{ carrier: 'AM', flightNumber: '1234' },
|
||||
{ carrier: 'FV', flightNumber: '5678' },
|
||||
] as OperatorModel[],
|
||||
},
|
||||
} as Leg,
|
||||
{ arrival: {} } as Leg,
|
||||
] as Leg[],
|
||||
expected: 'AM 1234, FV 5678',
|
||||
},
|
||||
{
|
||||
legs: [
|
||||
{
|
||||
operatingBy: {
|
||||
operators: [
|
||||
{ carrier: 'AM', flightNumber: '1234' },
|
||||
{ carrier: 'FV', flightNumber: '5678' },
|
||||
] as OperatorModel[],
|
||||
},
|
||||
} as Leg,
|
||||
{ operatingBy: { operators: [{ carrier: 'NV', flightNumber: '3456' }] as OperatorModel[] } } as Leg,
|
||||
] as Leg[],
|
||||
expected: 'AM 1234, FV 5678, NV 3456',
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
expect(pipe.transform(testCase.legs)).toEqual(testCase.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { Leg } from '../models-legacy';
|
||||
|
||||
@Pipe({
|
||||
name: 'codesharing',
|
||||
})
|
||||
export class CodesharingPipe implements PipeTransform {
|
||||
transform(legOrLegs: Leg | Leg[]): string {
|
||||
if (!legOrLegs) return '';
|
||||
|
||||
if (!Array.isArray(legOrLegs)) legOrLegs = [legOrLegs];
|
||||
|
||||
const operators = legOrLegs
|
||||
.filter((leg) => leg.operatingBy?.operators)
|
||||
.map((leg) => leg.operatingBy?.operators)
|
||||
.reduce((a, c) => a.concat(c), []);
|
||||
|
||||
return operators
|
||||
.map((operator) => `${operator.carrier} ${operator.flightNumber}`)
|
||||
.join(', ');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { DayChangePipe } from './day-change.pipe';
|
||||
|
||||
describe('DatePipe', () => {
|
||||
const date = new Date(2021, 10, 22, 17, 34);
|
||||
const translate: TranslatePipe = {
|
||||
transform: jasmine.createSpy('transform').withArgs('SHARED.DAY').and.returnValue('day'),
|
||||
} as any;
|
||||
let pipe: DayChangePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
pipe = new DayChangePipe(translate);
|
||||
});
|
||||
it('should return empty on empty input', () => {
|
||||
expect(pipe.transform(null)).toBe('');
|
||||
expect(pipe.transform(undefined)).toBe('');
|
||||
expect(pipe.transform('' as any)).toBe('');
|
||||
expect(pipe.transform({ value: null } as any)).toBe('');
|
||||
expect(pipe.transform({ value: 0 } as any)).toBe('');
|
||||
expect(pipe.transform({ value: 9, title: '' }, null)).toBe('');
|
||||
expect(pipe.transform({ value: 9, title: '' }, undefined)).toBe('');
|
||||
});
|
||||
|
||||
it('should format (+-)1 day', () => {
|
||||
expect(pipe.transform({ value: 1, title: '' })).toEqual('day');
|
||||
expect(pipe.transform({ value: -1, title: '' })).toEqual('day');
|
||||
});
|
||||
|
||||
it('should format more/less than 1 day with date as date', () => {
|
||||
expect(pipe.transform({ value: 2, title: '' }, date)).toEqual('22.11.2021');
|
||||
expect(pipe.transform({ value: -2, title: '' }, date)).toEqual('22.11.2021');
|
||||
});
|
||||
|
||||
it('should format more/less than 1 day with date as string', () => {
|
||||
expect(pipe.transform({ value: 2, title: '' }, date.toISOString())).toEqual('22.11.2021');
|
||||
expect(pipe.transform({ value: -2, title: '' }, date.toISOString())).toEqual('22.11.2021');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import * as moment from 'moment';
|
||||
import { DATE_FORMAT } from '../helpers';
|
||||
import { DayChangeModel } from './../models-legacy';
|
||||
@Pipe({
|
||||
name: 'dayChange',
|
||||
})
|
||||
export class DayChangePipe implements PipeTransform {
|
||||
constructor(private translate: TranslatePipe) {}
|
||||
|
||||
transform(model?: DayChangeModel, date?: string | Date): string {
|
||||
const $day = this.translate.transform('SHARED.DAY');
|
||||
|
||||
switch (model?.value) {
|
||||
case -1:
|
||||
case 1: {
|
||||
return $day;
|
||||
}
|
||||
case 0:
|
||||
case undefined: {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return this.handleDate(date);
|
||||
}
|
||||
|
||||
private handleDate(date?: string | Date) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return moment.parseZone(date).local(true).format(DATE_FORMAT)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { DurationPipe } from './duration.pipe';
|
||||
|
||||
describe('DurationPipe', () => {
|
||||
let translate: TranslatePipe;
|
||||
let pipe: DurationPipe;
|
||||
beforeEach(() => {
|
||||
translate = {
|
||||
transform: jasmine
|
||||
.createSpy('transform')
|
||||
.withArgs('SHARED.SHORT-HOUR')
|
||||
.and.returnValue('h')
|
||||
.withArgs('SHARED.SHORT-MIN')
|
||||
.and.returnValue('m')
|
||||
.withArgs('SHARED.SHORT-DAY')
|
||||
.and.returnValue('d'),
|
||||
} as any;
|
||||
pipe = new DurationPipe(translate);
|
||||
});
|
||||
it('should return empty on empty input', () => {
|
||||
expect(pipe.transform(null)).toBe('');
|
||||
expect(pipe.transform(undefined)).toBe('');
|
||||
expect(pipe.transform('' as any)).toBe('');
|
||||
});
|
||||
|
||||
it('should return 0h 0m on empty hour/minute input', () => {
|
||||
expect(pipe.transform({} as any)).toBe('0h 0m');
|
||||
});
|
||||
|
||||
it('should return time string on non empty hour/minute input', () => {
|
||||
expect(pipe.transform({ days: 0, minutes: 10, hours: 3 })).toBe('3h 10m');
|
||||
});
|
||||
|
||||
it('should return time string on non empty day/hour/minute input', () => {
|
||||
expect(pipe.transform({ days: 4, minutes: 10, hours: 3 })).toBe('4d 3h 10m');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { Duration } from '../models-legacy';
|
||||
|
||||
@Pipe({
|
||||
name: 'duration',
|
||||
})
|
||||
export class DurationPipe implements PipeTransform {
|
||||
constructor(private translate: TranslatePipe) {}
|
||||
|
||||
transform(value?: Duration): string {
|
||||
if (!value) return '';
|
||||
|
||||
const { days, hours = 0, minutes = 0 } = value;
|
||||
|
||||
if (days < 0 || hours < 0 || minutes < 0) {
|
||||
return this.translate.transform('FLIGHT-STATUSES.Unknown');
|
||||
}
|
||||
|
||||
const $days = days ? `${days}${this.translate.transform('SHARED.SHORT-DAY')} ` : '';
|
||||
|
||||
return `${$days}${hours}${this.translate.transform('SHARED.SHORT-HOUR')} ${minutes}${this.translate.transform('SHARED.SHORT-MIN')}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { RouteTypeLegacy } from '../enumerators';
|
||||
import { FlightNumberPipe } from './flight-number.pipe';
|
||||
|
||||
describe('FlightNumberPipe', () => {
|
||||
let pipe: FlightNumberPipe;
|
||||
beforeEach(() => {
|
||||
pipe = new FlightNumberPipe();
|
||||
});
|
||||
|
||||
it('should return empty for invalid inputs', () => {
|
||||
expect(pipe.transform(null)).toBe('');
|
||||
expect(pipe.transform(undefined)).toBe('');
|
||||
expect(pipe.transform({} as any)).toBe('');
|
||||
expect(pipe.transform({ routeType: RouteTypeLegacy.Direct } as any)).toBe('');
|
||||
});
|
||||
|
||||
it('should return "SU 1234" for direct flight', () => {
|
||||
expect(
|
||||
pipe.transform({
|
||||
routeType: RouteTypeLegacy.Direct,
|
||||
flightId: { carrier: 'SU', flightNumber: 1234 },
|
||||
} as any),
|
||||
).toBe('SU\u00a01234');
|
||||
});
|
||||
|
||||
it('should return "SU 1234, SU 4321" for connected flight', () => {
|
||||
expect(
|
||||
pipe.transform({
|
||||
routeType: RouteTypeLegacy.Connecting,
|
||||
flights: [{ flightId: { carrier: 'SU', flightNumber: 1234 } }, { flightId: { carrier: 'SU', flightNumber: 4321 } }],
|
||||
} as any),
|
||||
).toBe('SU\u00a01234, SU\u00a04321');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { getFlights } from '../models-legacy';
|
||||
import { FlightModel } from '../models-legacy/flight.model';
|
||||
|
||||
@Pipe({
|
||||
name: 'flightNumber',
|
||||
})
|
||||
export class FlightNumberPipe implements PipeTransform {
|
||||
transform(flight?: FlightModel): string {
|
||||
if (!flight) return '';
|
||||
return getFlights(flight)
|
||||
.filter((f) => !!f.flightId)
|
||||
.map((f) => `${f.flightId.carrier}\u00a0${f.flightId.flightNumber}${f.flightId.suffix}`)
|
||||
.join(', ');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user