mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
409 lines
14 KiB
TypeScript
409 lines
14 KiB
TypeScript
import { Injectable, OnDestroy } from '@angular/core';
|
|
import { DomainTaskCalendarService } from '@domain/task-calendar';
|
|
import { ComponentStore } from '@ngrx/component-store';
|
|
import { tapResponse } from '@ngrx/operators';
|
|
|
|
import { DisplayInfoDTO, InputDTO, QuerySettingsDTO } from '@swagger/eis';
|
|
import { CalendarIndicator } from '@ui/calendar';
|
|
import { DateAdapter } from '@ui/common';
|
|
import { Filter, FilterOption, SelectFilter, UiFilter, UiFilterMappingService } from '@ui/filter';
|
|
import { UiMessageModalComponent, UiModalRef, UiModalResult, UiModalService } from '@ui/modal';
|
|
import { clone } from 'lodash';
|
|
import { Observable, Subject, zip } from 'rxjs';
|
|
import { debounceTime, map, switchMap, tap, withLatestFrom, first, takeWhile, filter } from 'rxjs/operators';
|
|
import { InfoModalComponent } from './modals/info/info-modal.component';
|
|
import { PreInfoModalComponent } from './modals/preinfo/preinfo-modal.component';
|
|
import { TaskModalComponent } from './modals/task/task-modal.component';
|
|
|
|
export interface TaskCalendarState {
|
|
mode: 'week' | 'month';
|
|
selectedDate: Date;
|
|
displayedDate: Date;
|
|
displayInfos: DisplayInfoDTO[];
|
|
initialFilter: UiFilter;
|
|
filter: UiFilter;
|
|
message: string;
|
|
searchResults: DisplayInfoDTO[];
|
|
searchTarget: string;
|
|
fetchingFilter: boolean;
|
|
fetchingItems: boolean;
|
|
fetchingSearch: boolean;
|
|
hits: number;
|
|
}
|
|
|
|
@Injectable()
|
|
export class TaskCalendarStore extends ComponentStore<TaskCalendarState> implements OnDestroy {
|
|
private readonly _searchbarFocus$ = new Subject<void>();
|
|
|
|
get filter() {
|
|
return this.get((s) => s.filter);
|
|
}
|
|
|
|
readonly searchbarFocus$ = this._searchbarFocus$.asObservable();
|
|
|
|
readonly selectSelectedDate = this.select((s) => s.selectedDate);
|
|
|
|
readonly selectDisplayedDate = this.select((s) => s.displayedDate);
|
|
|
|
readonly selectSelectedYear = this.select(this.selectSelectedDate, (selectedDate) => this.dateAdapter.getYear(selectedDate));
|
|
|
|
readonly selectSelectedMonth = this.select(this.selectSelectedDate, (selectedDate) => this.dateAdapter.getMonth(selectedDate));
|
|
|
|
readonly selectedCalendarWeek = this.select(this.selectSelectedDate, (selectedDate) => this.dateAdapter.getCalendarWeek(selectedDate));
|
|
|
|
readonly selectDisplayInfos = this.select((s) => s.displayInfos);
|
|
|
|
readonly searchResults$ = this.select((s) => s.searchResults);
|
|
|
|
readonly hits$ = this.select((s) => s.hits);
|
|
|
|
readonly searchTarget$ = this.select((s) => s.searchTarget);
|
|
|
|
readonly selectCalendarIndicators = this.select(this.selectDisplayInfos, (displayItems) =>
|
|
displayItems.reduce<CalendarIndicator[]>((agg, item) => {
|
|
const calendarIndicator = this.mapDisplayInfoToCalendarIndicator(item);
|
|
if (
|
|
!agg.some(
|
|
(s) =>
|
|
this.dateAdapter.equals({ first: s.date, second: new Date(calendarIndicator.date), precision: 'day' }) &&
|
|
s.color === calendarIndicator.color,
|
|
) &&
|
|
!item.successor // do not show color indicator for items with successor
|
|
) {
|
|
return [...agg, calendarIndicator];
|
|
}
|
|
return agg;
|
|
}, []),
|
|
);
|
|
|
|
readonly selectInitialFilter = this.select((s) => s.initialFilter);
|
|
|
|
readonly selectFilter = this.select((s) => s.filter);
|
|
|
|
readonly selectFetchingFilter = this.select((s) => s.fetchingFilter);
|
|
|
|
readonly selectFetchingItems = this.select((s) => s.fetchingItems);
|
|
|
|
readonly selectFetchingSearch = this.select((s) => s.fetchingSearch);
|
|
|
|
readonly selectMessage = this.select((s) => s.message);
|
|
|
|
readonly selectStartStop = this.select((s) => {
|
|
const { mode, displayedDate } = s;
|
|
|
|
if (mode === 'week') {
|
|
const fdow = this.dateAdapter.getFirstDateOfWeek(displayedDate);
|
|
const fdowWithOffset = this.dateAdapter.addCalendarDays(fdow, -7);
|
|
const ldow = this.dateAdapter.getLastDateOfWeek(displayedDate);
|
|
|
|
return {
|
|
start: fdowWithOffset,
|
|
stop: ldow,
|
|
};
|
|
} else {
|
|
const month = this.dateAdapter.getDatesAndCalendarWeeksForMonth(displayedDate);
|
|
if (month.length > 0) {
|
|
const firstWeekOfMonth = month[0];
|
|
const lastWeekOfMonth = month[month.length - 1];
|
|
return {
|
|
start: firstWeekOfMonth.dates[0],
|
|
stop: lastWeekOfMonth.dates[lastWeekOfMonth.dates.length - 1],
|
|
};
|
|
}
|
|
}
|
|
});
|
|
|
|
hits = this.get((s) => s.hits);
|
|
|
|
constructor(
|
|
public domainTaskCalendarService: DomainTaskCalendarService,
|
|
private dateAdapter: DateAdapter,
|
|
private uiModal: UiModalService,
|
|
private uiFilterMappingService: UiFilterMappingService,
|
|
) {
|
|
super({
|
|
mode: 'month',
|
|
selectedDate: dateAdapter.today(),
|
|
displayedDate: dateAdapter.today(),
|
|
displayInfos: [],
|
|
initialFilter: undefined,
|
|
filter: undefined,
|
|
message: undefined,
|
|
fetchingFilter: false,
|
|
fetchingItems: false,
|
|
fetchingSearch: false,
|
|
searchResults: [],
|
|
hits: undefined,
|
|
searchTarget: '',
|
|
});
|
|
}
|
|
|
|
readonly setSelectedDate = this.updater((s, { date }: { date: Date }) => ({
|
|
...s,
|
|
selectedDate: date,
|
|
}));
|
|
|
|
readonly setDisplayedDate = this.updater((s, { date }: { date: Date }) => ({
|
|
...s,
|
|
displayedDate: date,
|
|
}));
|
|
|
|
readonly setFilter = this.updater((s, { filters }: { filters: UiFilter }) => ({
|
|
...s,
|
|
filter: filters,
|
|
}));
|
|
|
|
readonly setMode = this.updater((s, { mode }: { mode: 'week' | 'month' }) => ({
|
|
...s,
|
|
mode,
|
|
}));
|
|
|
|
readonly setMessage = this.updater((s, { message }: { message: string }) => ({
|
|
...s,
|
|
message,
|
|
}));
|
|
|
|
/**
|
|
* Der Key der Datums-Gruppe, zu der initial navigiert werden soll
|
|
*/
|
|
readonly setSearchTarget = this.updater((s, { searchTarget }: { searchTarget: string }) => ({
|
|
...s,
|
|
searchTarget,
|
|
}));
|
|
|
|
search = this.effect((options$: Observable<{ clear?: boolean }>) => {
|
|
const currentBranch$ = this.domainTaskCalendarService.currentBranchId$.pipe(filter((f) => !!f));
|
|
const selectFilter$ = this.selectFilter.pipe(filter((f) => !!f));
|
|
return options$.pipe(
|
|
tap(() => this.patchState({ fetchingSearch: true, searchTarget: '' })),
|
|
debounceTime(150),
|
|
switchMap((options) =>
|
|
zip(currentBranch$, selectFilter$).pipe(
|
|
switchMap(([branchId, filter]) => {
|
|
const querytoken = {
|
|
...filter?.getQueryToken(),
|
|
};
|
|
|
|
// Im Zeitraum von 6 Wochen in der Vergangenheit und 2 Wochen in der Zukunft abfragen
|
|
const start = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), -42);
|
|
const stop = this.dateAdapter.addCalendarDays(this.dateAdapter.today(), 14);
|
|
return this.domainTaskCalendarService
|
|
.getInfos({
|
|
...querytoken,
|
|
filter: {
|
|
timespan: `"${start.toISOString()}"-"${stop?.toISOString()}"`,
|
|
...querytoken?.filter,
|
|
branch_id: String(branchId),
|
|
},
|
|
})
|
|
.pipe(
|
|
tapResponse(
|
|
(response) => {
|
|
if (!response.error) {
|
|
response = this.preparePreInfos(response);
|
|
|
|
const results = this.get((s) => s.searchResults);
|
|
const searchResults = results.length > 0 && !options?.clear ? [...results, ...response.result] : [...response.result];
|
|
const sorted = searchResults.sort((a, b) =>
|
|
this.dateAdapter.findClosestDate(
|
|
new Date(a.taskDate || a.publicationDate),
|
|
new Date(b.taskDate || b.publicationDate),
|
|
),
|
|
);
|
|
const searchTarget =
|
|
sorted?.length > 0
|
|
? this.domainTaskCalendarService.getDateGroupKey(sorted[0].taskDate || sorted[0].publicationDate)
|
|
: '';
|
|
|
|
this.patchState({
|
|
searchResults,
|
|
searchTarget,
|
|
fetchingSearch: false,
|
|
message: undefined,
|
|
hits: response.hits,
|
|
});
|
|
} else {
|
|
this.uiModal.open({ content: UiMessageModalComponent, data: response });
|
|
this.patchState({ searchResults: [], fetchingSearch: false, message: 'Keine Suchergebnisse' });
|
|
}
|
|
},
|
|
(error) => {
|
|
console.error(error);
|
|
this.patchState({ searchResults: [], fetchingSearch: false, message: 'Keine Suchergebnisse' });
|
|
},
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
resetSearch() {
|
|
this.patchState({ searchResults: [], hits: undefined });
|
|
}
|
|
|
|
focusSearchbar() {
|
|
this._searchbarFocus$.next();
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this._searchbarFocus$.complete();
|
|
}
|
|
|
|
loadItems = this.effect(($: Observable<void>) => {
|
|
const currentBranch$ = this.domainTaskCalendarService.currentBranchId$.pipe(filter((f) => !!f));
|
|
const selectStartStop$ = this.selectStartStop.pipe(filter((f) => !!f));
|
|
const selectInitialFilter$ = this.selectInitialFilter.pipe(filter((f) => !!f));
|
|
|
|
return $.pipe(
|
|
tap(() => this.patchState({ fetchingItems: true })),
|
|
debounceTime(250),
|
|
switchMap((_) =>
|
|
zip(currentBranch$, selectStartStop$, selectInitialFilter$).pipe(
|
|
switchMap(([branchId, date, filter]) => {
|
|
const querytoken = { ...filter?.getQueryToken() };
|
|
return this.domainTaskCalendarService
|
|
.getInfos({
|
|
...querytoken,
|
|
filter: {
|
|
...querytoken?.filter,
|
|
branch_id: String(branchId),
|
|
timespan: `"${date?.start?.toISOString()}"-"${date.stop?.toISOString()}"`,
|
|
},
|
|
})
|
|
.pipe(
|
|
tapResponse(
|
|
(response) => {
|
|
if (!response.error) {
|
|
response = this.preparePreInfos(response);
|
|
this.patchState({ displayInfos: response.result, fetchingItems: false, message: undefined });
|
|
} else {
|
|
this.uiModal.open({
|
|
content: UiMessageModalComponent,
|
|
data: response,
|
|
});
|
|
this.patchState({ displayInfos: [], fetchingItems: false, message: 'Keine Suchergebnisse' });
|
|
}
|
|
},
|
|
(error) => {
|
|
console.error(error);
|
|
this.patchState({ displayInfos: [], fetchingItems: false, message: 'Keine Suchergebnisse' });
|
|
},
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
preparePreInfos(response) {
|
|
const preInfos = response.result.filter((info) => this.domainTaskCalendarService.getInfoType(info) === 'PreInfo');
|
|
preInfos.forEach((info) => {
|
|
const preInfoTask = clone(info);
|
|
delete preInfoTask.publicationDate;
|
|
response.result.push(preInfoTask);
|
|
});
|
|
return response;
|
|
}
|
|
|
|
readonly loadFilter = this.effect(($: Observable<void>) =>
|
|
$.pipe(
|
|
tap(() => this.patchState({ fetchingFilter: true })),
|
|
switchMap((_) => this.domainTaskCalendarService.getSettings()),
|
|
tapResponse(
|
|
(response: QuerySettingsDTO) => {
|
|
this.patchState({ filter: UiFilter.create(response), initialFilter: UiFilter.create(response), fetchingFilter: false });
|
|
},
|
|
(error) => {
|
|
console.error(error);
|
|
this.patchState({ filter: undefined, initialFilter: undefined, fetchingFilter: false });
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
open(displayInfoDTO: DisplayInfoDTO) {
|
|
const type = this.domainTaskCalendarService.getInfoType(displayInfoDTO);
|
|
let taskModalRef: UiModalRef<{ successorId: number }, DisplayInfoDTO>;
|
|
|
|
switch (type) {
|
|
case 'Info':
|
|
taskModalRef = this.uiModal.open({
|
|
content: InfoModalComponent,
|
|
data: displayInfoDTO,
|
|
});
|
|
break;
|
|
|
|
case 'PreInfo':
|
|
taskModalRef = this.uiModal.open({
|
|
content: PreInfoModalComponent,
|
|
data: displayInfoDTO,
|
|
});
|
|
break;
|
|
|
|
case 'Task':
|
|
taskModalRef = this.uiModal.open({
|
|
content: TaskModalComponent,
|
|
data: displayInfoDTO,
|
|
});
|
|
break;
|
|
}
|
|
|
|
taskModalRef?.afterClosed$.subscribe({
|
|
next: async (result: UiModalResult<{ successorId: number }>) => {
|
|
if (result.data?.successorId) {
|
|
const info = await this.domainTaskCalendarService
|
|
.getInfoById({ infoId: result.data.successorId })
|
|
.pipe(map((res) => res.result))
|
|
.toPromise();
|
|
if (info) {
|
|
this.open(info);
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
let hasChanged = false;
|
|
taskModalRef?.afterChanged$.subscribe({
|
|
next: (changed) => (hasChanged = changed),
|
|
complete: async () => {
|
|
if (hasChanged) {
|
|
this.loadItems();
|
|
const searchResults = await this.searchResults$.pipe(first()).toPromise();
|
|
if (searchResults?.length > 0) {
|
|
this.search({ clear: true });
|
|
}
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
mapInputArrayToFilterArray(source: InputDTO[]): SelectFilter[] {
|
|
const mappedArr = source.map((input) => {
|
|
return this.uiFilterMappingService.fromInputDto(input as any);
|
|
});
|
|
return mappedArr;
|
|
}
|
|
|
|
mapFilterArrayToStringDictionary(source: Filter[]): { [key: string]: string } {
|
|
const dict: { [key: string]: string } = {};
|
|
for (const filter of source) {
|
|
const options: FilterOption[] = filter.options;
|
|
const selected = options.filter((o) => o.selected);
|
|
if (selected.length > 0) {
|
|
dict[filter.key] = selected.map((o) => o.id).join(';');
|
|
}
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
mapDisplayInfoToCalendarIndicator(source: DisplayInfoDTO): CalendarIndicator {
|
|
return {
|
|
date: this.domainTaskCalendarService.getDisplayInfoDate(source),
|
|
color: this.domainTaskCalendarService.getProcessingStatusColorCode(source),
|
|
};
|
|
}
|
|
}
|