feat: add import to excel

Made at the request of the customer
This commit is contained in:
Polianin Nikita 2024-10-27 08:29:30 +03:00
parent 7c66f31bac
commit 38b877608f
7 changed files with 141 additions and 35 deletions

View File

@ -23,7 +23,7 @@ export enum AvailableVersion {
@Injectable() @Injectable()
export default abstract class ApiService { export default abstract class ApiService {
constructor(private http: HttpClient, private notify: ToastrService, private router: Router) { constructor(protected http: HttpClient, protected notify: ToastrService, private router: Router) {
} }
private apiUrl = environment.apiUrl; private apiUrl = environment.apiUrl;
@ -65,7 +65,7 @@ export default abstract class ApiService {
return this.http.request<Type>(method, doneEndpoint, { return this.http.request<Type>(method, doneEndpoint, {
withCredentials: request.withCredentials, withCredentials: request.withCredentials,
headers: request.httpHeaders, headers: request.httpHeaders,
body: request.data body: request.data,
}).pipe( }).pipe(
catchError(error => { catchError(error => {
if (!secondTry && error.status === 401) if (!secondTry && error.status === 401)

View File

@ -0,0 +1,22 @@
import {Injectable} from "@angular/core";
import ApiService, {AvailableVersion} from "@api/api.service";
import {ScheduleRequest} from "@api/v1/scheduleRequest";
@Injectable()
export class ImportService extends ApiService {
public readonly basePath = 'Import/';
public readonly version = AvailableVersion.v1;
public importToExcel(data: ScheduleRequest) {
let request = this.createRequestBuilder()
.setData(data)
.setEndpoint('ImportToExcel')
.build;
console.log(this.combinedUrl(request));
console.log(data);
return this.http.post(this.combinedUrl(request), data, {
responseType: 'blob'
});
}
}

View File

@ -18,6 +18,7 @@ import {ProfessorService} from "@api/v1/professor.service";
import {AuthRoles} from "@model/AuthRoles"; import {AuthRoles} from "@model/AuthRoles";
import {HasRoleDirective} from "@/directives/has-role.directive"; import {HasRoleDirective} from "@/directives/has-role.directive";
import {TabSelectType, TabStorageService} from "@service/tab-storage.service"; import {TabSelectType, TabStorageService} from "@service/tab-storage.service";
import {ScheduleRequest} from "@api/v1/scheduleRequest";
export enum TabsSelect { export enum TabsSelect {
Group, Group,
@ -48,7 +49,7 @@ export enum TabsSelect {
}) })
export class TabsComponent implements AfterViewInit { export class TabsComponent implements AfterViewInit {
@Output() eventResult = new EventEmitter<[TabsSelect, number, Observable<ScheduleResponse[]>]>(); @Output() eventResult = new EventEmitter<[TabsSelect, number, Observable<ScheduleResponse[]>, ScheduleRequest]>();
private currentTab: number = -1; private currentTab: number = -1;
constructor(private scheduleApi: ScheduleService, constructor(private scheduleApi: ScheduleService,
@ -68,7 +69,8 @@ export class TabsComponent implements AfterViewInit {
[ [
TabsSelect.Group, TabsSelect.Group,
event, event,
this.scheduleApi.getByGroup(event) this.scheduleApi.getByGroup(event),
{groups: [event]}
] ]
)); ));
@ -76,7 +78,8 @@ export class TabsComponent implements AfterViewInit {
[ [
TabsSelect.Professor, TabsSelect.Professor,
event, event,
this.scheduleApi.getByProfessor(event) this.scheduleApi.getByProfessor(event),
{professors: [event]}
] ]
)); ));
@ -84,7 +87,8 @@ export class TabsComponent implements AfterViewInit {
[ [
TabsSelect.LectureHall, TabsSelect.LectureHall,
event, event,
this.scheduleApi.getByLectureHall(event) this.scheduleApi.getByLectureHall(event),
{lectureHalls: [event]}
] ]
)); ));
@ -196,16 +200,19 @@ export class TabsComponent implements AfterViewInit {
protected readonly AuthRoles = AuthRoles; protected readonly AuthRoles = AuthRoles;
protected otherFilter() { protected otherFilter() {
this.eventResult.emit( const data: ScheduleRequest = ({
[
TabsSelect.Other,
0,
this.scheduleApi.postSchedule(({
groups: this.groupEx.selectedIds, groups: this.groupEx.selectedIds,
disciplines: this.disciplineEx.selectedIds, disciplines: this.disciplineEx.selectedIds,
professors: this.professorEx.selectedIds, professors: this.professorEx.selectedIds,
lectureHalls: this.lectureHallEx.selectedIds lectureHalls: this.lectureHallEx.selectedIds
})) });
this.eventResult.emit(
[
TabsSelect.Other,
0,
this.scheduleApi.postSchedule(data),
data
] ]
); );
} }

View File

@ -0,0 +1,8 @@
<h1 mat-dialog-title>Подтверждение</h1>
<div mat-dialog-content>
<p>Вы уверены, что хотите запросить Excel с выбранными данными?</p>
</div>
<div mat-dialog-actions>
<button mat-button color="accent" (click)="onConfirm()">Запросить</button>
<button mat-button (click)="onCancel()">Отмена</button>
</div>

View File

@ -0,0 +1,27 @@
import { Component } from '@angular/core';
import {MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle} from '@angular/material/dialog';
import {MatButton} from "@angular/material/button";
@Component({
selector: 'app-confirm-dialog',
templateUrl: './confirm-dialog.component.html',
imports: [
MatDialogTitle,
MatDialogContent,
MatDialogActions,
MatButton
],
standalone: true
})
export class ConfirmDialogComponent {
constructor(public dialogRef: MatDialogRef<ConfirmDialogComponent>) { }
protected onConfirm(): void {
this.dialogRef.close(true);
}
protected onCancel(): void {
this.dialogRef.close(false);
}
}

View File

@ -12,7 +12,12 @@
<app-table [currentWeek]="currentWeek" [startWeek]="startWeek" [data]="data" [isLoad]="isLoadTable" [disciplineWithWeeks]="disciplineWithWeeks"/> <app-table [currentWeek]="currentWeek" [startWeek]="startWeek" [data]="data" [isLoad]="isLoadTable" [disciplineWithWeeks]="disciplineWithWeeks"/>
</mat-sidenav-content> </mat-sidenav-content>
<mat-sidenav-content> <mat-sidenav-content style="display: flex; justify-content: space-between; align-items: center;">
<mat-checkbox (change)="changeDisciplineWeeksView($event.checked)" [checked]="disciplineWithWeeks">Показать недели в дисциплине</mat-checkbox> <mat-checkbox (change)="changeDisciplineWeeksView($event.checked)" [checked]="disciplineWithWeeks">Показать недели в дисциплине</mat-checkbox>
@if(excelImportLoader) {
<app-data-spinner/>
} @else {
<button mat-button (click)="openDialog()" *appHasRole="AuthRoles.Admin">Импортировать расписание (.xlsx)</button>
}
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>

View File

@ -1,55 +1,62 @@
import {Component, LOCALE_ID, OnInit, ViewChild} from '@angular/core'; import {Component, LOCALE_ID, ViewChild} from '@angular/core';
import {TableComponent} from "@component/schedule/table/table.component";
import {MatFormField, MatInput} from "@angular/material/input";
import {MatButton} from "@angular/material/button";
import {FormsModule} from "@angular/forms";
import {AdditionalText, TableHeaderComponent} from "@component/schedule/table-header/table-header.component"; import {AdditionalText, TableHeaderComponent} from "@component/schedule/table-header/table-header.component";
import {addDays, weekInYear} from "@progress/kendo-date-math"; import {addDays, weekInYear} from "@progress/kendo-date-math";
import {MatCard} from "@angular/material/card";
import {MatSidenavModule} from "@angular/material/sidenav";
import {TabsComponent, TabsSelect} from "@component/schedule/tabs/tabs.component"; import {TabsComponent, TabsSelect} from "@component/schedule/tabs/tabs.component";
import {catchError, Observable} from "rxjs"; import {catchError, Observable} from "rxjs";
import {ScheduleService} from "@api/v1/schedule.service"; import {ScheduleService} from "@api/v1/schedule.service";
import {ScheduleResponse} from "@api/v1/scheduleResponse"; import {ScheduleResponse} from "@api/v1/scheduleResponse";
import {PeriodTimes} from "@model/pairPeriodTime"; import {PeriodTimes} from "@model/pairPeriodTime";
import {MatCheckbox} from "@angular/material/checkbox";
import {ActivatedRoute} from "@angular/router"; import {ActivatedRoute} from "@angular/router";
import {TabStorageService} from "@service/tab-storage.service"; import {TabStorageService} from "@service/tab-storage.service";
import {MatDialog} from "@angular/material/dialog";
import {ConfirmDialogComponent} from "@page/schedule/confirm-dialog.component";
import {AuthRoles} from "@model/AuthRoles";
import {ImportService} from "@api/v1/import.service";
import {ScheduleRequest} from "@api/v1/scheduleRequest";
import {ToastrService} from "ngx-toastr";
import {MatSidenavModule} from "@angular/material/sidenav";
import {TableComponent} from "@component/schedule/table/table.component";
import {MatCheckbox} from "@angular/material/checkbox";
import {DataSpinnerComponent} from "@component/common/data-spinner/data-spinner.component";
import {MatButton} from "@angular/material/button";
import {HasRoleDirective} from "@/directives/has-role.directive";
@Component({ @Component({
selector: 'app-schedule', selector: 'app-schedule',
standalone: true, standalone: true,
imports: [ imports: [
TableComponent,
MatInput,
MatFormField,
MatButton,
FormsModule,
TableHeaderComponent,
MatCard,
MatSidenavModule, MatSidenavModule,
TabsComponent, TabsComponent,
MatCheckbox TableHeaderComponent,
TableComponent,
MatCheckbox,
DataSpinnerComponent,
MatButton,
HasRoleDirective
], ],
templateUrl: './schedule.component.html', templateUrl: './schedule.component.html',
styleUrl: './schedule.component.css', styleUrl: './schedule.component.css',
providers: [ providers: [
ScheduleService, ScheduleService,
ImportService,
{provide: LOCALE_ID, useValue: 'ru-RU'} {provide: LOCALE_ID, useValue: 'ru-RU'}
] ]
}) })
export class ScheduleComponent implements OnInit { export class ScheduleComponent {
private lastRequest: ScheduleRequest | null = null;
protected startWeek: Date; protected startWeek: Date;
protected data: ScheduleResponse[] = []; protected data: ScheduleResponse[] = [];
protected startTerm: Date; protected startTerm: Date;
protected isLoadTable: boolean = false; protected isLoadTable: boolean = false;
protected pairPeriods: PeriodTimes = {}; protected pairPeriods: PeriodTimes = {};
protected disciplineWithWeeks: boolean = false; protected disciplineWithWeeks: boolean = false;
protected excelImportLoader: boolean = false;
@ViewChild('tableHeader') childComponent!: TableHeaderComponent; @ViewChild('tableHeader') childComponent!: TableHeaderComponent;
constructor(api: ScheduleService, route: ActivatedRoute) { constructor(api: ScheduleService, route: ActivatedRoute, private importApi: ImportService, private notify: ToastrService, public dialog: MatDialog) {
route.queryParams.subscribe(params => { route.queryParams.subscribe(params => {
TabStorageService.selectDataFromQuery(params); TabStorageService.selectDataFromQuery(params);
}); });
@ -71,11 +78,10 @@ export class ScheduleComponent implements OnInit {
}); });
} }
ngOnInit(): void { protected result(data: [TabsSelect, number, Observable<ScheduleResponse[]>, ScheduleRequest]) {
}
protected result(data: [TabsSelect, number, Observable<ScheduleResponse[]>]) {
this.isLoadTable = true; this.isLoadTable = true;
this.lastRequest = data[3];
data[2] data[2]
.pipe(catchError(error => { .pipe(catchError(error => {
this.data = []; this.data = [];
@ -147,4 +153,35 @@ export class ScheduleComponent implements OnInit {
localStorage.setItem('disciplineWithWeeks', checked.toString()); localStorage.setItem('disciplineWithWeeks', checked.toString());
this.disciplineWithWeeks = checked; this.disciplineWithWeeks = checked;
} }
protected openDialog() {
if (this.lastRequest == null) {
this.notify.error("It is not possible to make an import request because the table data has not been selected", "Import error");
return;
}
const dialogRef = this.dialog.open(ConfirmDialogComponent);
dialogRef.afterClosed().subscribe(result => {
if (result && this.lastRequest != null) {
this.excelImportLoader = true;
this.importApi.importToExcel(this.lastRequest).subscribe({
next: (blob: Blob) => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'schedule.xlsx';
a.click();
window.URL.revokeObjectURL(url);
this.excelImportLoader = false;
},
error: _ => {
this.excelImportLoader = false;
this.notify.error("Failed to import Excel file");
}
});
}
});
}
protected readonly AuthRoles = AuthRoles;
} }