init dashboard
This commit is contained in:
+200
@@ -0,0 +1,200 @@
|
||||
import {
|
||||
CdkVirtualScrollViewport,
|
||||
ScrollingModule,
|
||||
} from '@angular/cdk/scrolling';
|
||||
import { AsyncPipe, CurrencyPipe, NgClass, NgFor, NgIf } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Input,
|
||||
ViewChild,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { DialogService } from '@ngneat/dialog';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
Observable,
|
||||
combineLatest,
|
||||
debounceTime,
|
||||
filter,
|
||||
map,
|
||||
mergeMap,
|
||||
scan,
|
||||
switchMap,
|
||||
tap,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
AccountingFilterArgs,
|
||||
AccountingState,
|
||||
Ticket,
|
||||
Tour,
|
||||
} from '../../../core/data-access/graphql/generated/generated';
|
||||
import { AccountingTourViewComponent } from '../accounting-tour-view/accounting-tour-view.component';
|
||||
import { DashboardAccountingService } from '../dashboard-accounting.service';
|
||||
import { AccountingItemComponent } from './accounting-item.component';
|
||||
import { AccountingLaneFooterComponent } from './accounting-lane-footer.component';
|
||||
import { AccountingLaneHeaderComponent } from './accounting-lane-header.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dks-accounting-lane',
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './accounting-lane.component.html',
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
@apply rounded bg-gray-200 h-full w-full grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
`,
|
||||
],
|
||||
imports: [
|
||||
AccountingLaneHeaderComponent,
|
||||
AccountingLaneFooterComponent,
|
||||
AccountingItemComponent,
|
||||
NgFor,
|
||||
NgIf,
|
||||
NgClass,
|
||||
AsyncPipe,
|
||||
CurrencyPipe,
|
||||
ScrollingModule,
|
||||
],
|
||||
})
|
||||
export class AccountingLaneComponent {
|
||||
private dialog = inject(DialogService);
|
||||
private accountingService = inject(DashboardAccountingService);
|
||||
|
||||
private accountingState$ = new BehaviorSubject<AccountingState | undefined>(
|
||||
undefined
|
||||
);
|
||||
private accountingFilter$ = new BehaviorSubject<AccountingFilterArgs>({});
|
||||
|
||||
@Input() set accountingState(accountingState: AccountingState) {
|
||||
this.accountingState$.next(accountingState);
|
||||
}
|
||||
get accountingState() {
|
||||
return this.accountingState$.getValue() as AccountingState;
|
||||
}
|
||||
|
||||
@ViewChild(CdkVirtualScrollViewport)
|
||||
viewport!: CdkVirtualScrollViewport;
|
||||
|
||||
AccountingState = AccountingState;
|
||||
|
||||
take = 20;
|
||||
hasNextPage = true;
|
||||
nextPage = '';
|
||||
|
||||
cursor$ = new BehaviorSubject<string>('');
|
||||
accountingStateMeta$ = this.accountingState$.pipe(
|
||||
filter(Boolean),
|
||||
switchMap((accountingState) =>
|
||||
this.accountingService.fetchAccountingStateMeta(accountingState)
|
||||
)
|
||||
);
|
||||
tours$: Observable<Tour[]>;
|
||||
|
||||
constructor() {
|
||||
const batchMap: Observable<Record<string, Tour & { ticket: Ticket }>> =
|
||||
combineLatest([this.cursor$, this.accountingFilter$]).pipe(
|
||||
debounceTime(200),
|
||||
mergeMap(([cursor, filter]) =>
|
||||
this.fetchTours(cursor, filter).pipe(
|
||||
map((tours) => ({ tours, cursor }))
|
||||
)
|
||||
),
|
||||
scan((acc, { tours, cursor }) => {
|
||||
const normalizedCursor = this.getNormalizedCursor(cursor);
|
||||
const existingTours = Object.entries(acc)
|
||||
.filter(
|
||||
([id]) =>
|
||||
id.split('_').at(0) !== normalizedCursor ||
|
||||
Object.keys(tours).includes(id.split('_').at(1) ?? '')
|
||||
)
|
||||
.reduce(
|
||||
(acc, [id, tour]) => ({
|
||||
...acc,
|
||||
[`${normalizedCursor}_${id}`]: tour,
|
||||
}),
|
||||
{} as Record<string, Tour & { ticket: Ticket }>
|
||||
);
|
||||
|
||||
return { ...existingTours, ...tours };
|
||||
}, {} as Record<string, Tour & { ticket: Ticket }>)
|
||||
);
|
||||
this.tours$ = batchMap.pipe(
|
||||
map((tourNodes) =>
|
||||
Object.values(tourNodes).sort(
|
||||
({ createdAt: aCreated }, { createdAt: bCreated }) =>
|
||||
new Date(bCreated).getTime() - new Date(aCreated).getTime()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
openTourView(tour: Tour & { ticket: Ticket }): void {
|
||||
const dialogRef = this.dialog.open(AccountingTourViewComponent, {
|
||||
data: tour,
|
||||
width: '60vw',
|
||||
minHeight: '400px',
|
||||
});
|
||||
dialogRef.afterClosed$
|
||||
.pipe(tap((result) => result && this.openTourView(result)))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
trackByFn(index: number, tour: Tour): string {
|
||||
return tour.id;
|
||||
}
|
||||
|
||||
fetchNextPage() {
|
||||
if (!this.hasNextPage) {
|
||||
return;
|
||||
}
|
||||
const start = this.viewport.getRenderedRange().start;
|
||||
const end = this.viewport.getRenderedRange().end;
|
||||
const total = this.viewport.getDataLength();
|
||||
|
||||
if (this.cursor$.getValue() !== '' && start === 0) {
|
||||
this.cursor$.next('');
|
||||
} else if (total - end < 5) {
|
||||
this.cursor$.next(this.nextPage);
|
||||
}
|
||||
}
|
||||
|
||||
private fetchTours(
|
||||
cursor: string,
|
||||
filter: AccountingFilterArgs
|
||||
): Observable<Record<string, Tour & { ticket: Ticket }>> {
|
||||
const normalizedCursor = this.getNormalizedCursor(cursor);
|
||||
|
||||
return this.accountingService
|
||||
.fetchToursByState(this.accountingState, cursor, filter, this.take)
|
||||
.pipe(
|
||||
tap(({ endCursor, hasNextPage }) => {
|
||||
this.nextPage = endCursor;
|
||||
this.hasNextPage = hasNextPage;
|
||||
}),
|
||||
map(({ nodes }) =>
|
||||
nodes?.reduce(
|
||||
(acc, cur) => ({
|
||||
...acc,
|
||||
[`${normalizedCursor}_${cur.id}`]: cur as Tour & {
|
||||
ticket: Ticket;
|
||||
},
|
||||
}),
|
||||
{} as Record<string, Tour & { ticket: Ticket }>
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private getNormalizedCursor(cursor: string) {
|
||||
return cursor === '' ? 'MA==' : cursor;
|
||||
}
|
||||
|
||||
filterChanged(filter: AccountingFilterArgs) {
|
||||
this.accountingFilter$.next(filter);
|
||||
this.cursor$.next('');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user