Compare commits
No commits in common. "99958a23832e85e796d5a5331d7fbed578b24cbd" and "72a5f3740481ee2a74c3cd15deedec0deed679ef" have entirely different histories.
99958a2383
...
72a5f37404
@ -23,7 +23,7 @@ export enum AvailableVersion {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default abstract class ApiService {
|
export default abstract class ApiService {
|
||||||
constructor(protected http: HttpClient, protected notify: ToastrService, private router: Router) {
|
constructor(private http: HttpClient, private 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)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import ApiService, {AvailableVersion} from "@api/api.service";
|
import ApiService, {AvailableVersion} from "@api/api.service";
|
||||||
import {FacultyResponse} from "@api/v1/facultyResponse";
|
import {FacultyResponse} from "@api/v1/facultyResponse";
|
||||||
|
import {FacultyDetailsResponse} from "@api/v1/facultyDetailsResponse";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FacultyService extends ApiService {
|
export class FacultyService extends ApiService {
|
||||||
@ -14,4 +15,8 @@ export class FacultyService extends ApiService {
|
|||||||
|
|
||||||
return this.get<FacultyResponse[]>(request);
|
return this.get<FacultyResponse[]>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getById(id: number) {
|
||||||
|
return this.get<FacultyDetailsResponse>(id.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
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'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,7 +18,6 @@ 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,
|
||||||
@ -49,7 +48,7 @@ export enum TabsSelect {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export class TabsComponent implements AfterViewInit {
|
export class TabsComponent implements AfterViewInit {
|
||||||
@Output() eventResult = new EventEmitter<[TabsSelect, number, Observable<ScheduleResponse[]>, ScheduleRequest]>();
|
@Output() eventResult = new EventEmitter<[TabsSelect, number, Observable<ScheduleResponse[]>]>();
|
||||||
private currentTab: number = -1;
|
private currentTab: number = -1;
|
||||||
|
|
||||||
constructor(private scheduleApi: ScheduleService,
|
constructor(private scheduleApi: ScheduleService,
|
||||||
@ -69,8 +68,7 @@ export class TabsComponent implements AfterViewInit {
|
|||||||
[
|
[
|
||||||
TabsSelect.Group,
|
TabsSelect.Group,
|
||||||
event,
|
event,
|
||||||
this.scheduleApi.getByGroup(event),
|
this.scheduleApi.getByGroup(event)
|
||||||
{groups: [event]}
|
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -78,8 +76,7 @@ export class TabsComponent implements AfterViewInit {
|
|||||||
[
|
[
|
||||||
TabsSelect.Professor,
|
TabsSelect.Professor,
|
||||||
event,
|
event,
|
||||||
this.scheduleApi.getByProfessor(event),
|
this.scheduleApi.getByProfessor(event)
|
||||||
{professors: [event]}
|
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -87,8 +84,7 @@ export class TabsComponent implements AfterViewInit {
|
|||||||
[
|
[
|
||||||
TabsSelect.LectureHall,
|
TabsSelect.LectureHall,
|
||||||
event,
|
event,
|
||||||
this.scheduleApi.getByLectureHall(event),
|
this.scheduleApi.getByLectureHall(event)
|
||||||
{lectureHalls: [event]}
|
|
||||||
]
|
]
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -200,19 +196,16 @@ export class TabsComponent implements AfterViewInit {
|
|||||||
protected readonly AuthRoles = AuthRoles;
|
protected readonly AuthRoles = AuthRoles;
|
||||||
|
|
||||||
protected otherFilter() {
|
protected otherFilter() {
|
||||||
const data: ScheduleRequest = ({
|
|
||||||
groups: this.groupEx.selectedIds,
|
|
||||||
disciplines: this.disciplineEx.selectedIds,
|
|
||||||
professors: this.professorEx.selectedIds,
|
|
||||||
lectureHalls: this.lectureHallEx.selectedIds
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventResult.emit(
|
this.eventResult.emit(
|
||||||
[
|
[
|
||||||
TabsSelect.Other,
|
TabsSelect.Other,
|
||||||
0,
|
0,
|
||||||
this.scheduleApi.postSchedule(data),
|
this.scheduleApi.postSchedule(({
|
||||||
data
|
groups: this.groupEx.selectedIds,
|
||||||
|
disciplines: this.disciplineEx.selectedIds,
|
||||||
|
professors: this.professorEx.selectedIds,
|
||||||
|
lectureHalls: this.lectureHallEx.selectedIds
|
||||||
|
}))
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
<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>
|
|
@ -1,27 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,12 +12,7 @@
|
|||||||
<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 style="display: flex; justify-content: space-between; align-items: center;">
|
<mat-sidenav-content>
|
||||||
<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>
|
||||||
|
@ -1,62 +1,55 @@
|
|||||||
import {Component, LOCALE_ID, ViewChild} from '@angular/core';
|
import {Component, LOCALE_ID, OnInit, 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,
|
||||||
TableHeaderComponent,
|
MatCheckbox
|
||||||
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 {
|
export class ScheduleComponent implements OnInit {
|
||||||
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, private importApi: ImportService, private notify: ToastrService, public dialog: MatDialog) {
|
constructor(api: ScheduleService, route: ActivatedRoute) {
|
||||||
route.queryParams.subscribe(params => {
|
route.queryParams.subscribe(params => {
|
||||||
TabStorageService.selectDataFromQuery(params);
|
TabStorageService.selectDataFromQuery(params);
|
||||||
});
|
});
|
||||||
@ -78,10 +71,11 @@ export class ScheduleComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected result(data: [TabsSelect, number, Observable<ScheduleResponse[]>, ScheduleRequest]) {
|
ngOnInit(): void {
|
||||||
this.isLoadTable = true;
|
}
|
||||||
this.lastRequest = data[3];
|
|
||||||
|
|
||||||
|
protected result(data: [TabsSelect, number, Observable<ScheduleResponse[]>]) {
|
||||||
|
this.isLoadTable = true;
|
||||||
data[2]
|
data[2]
|
||||||
.pipe(catchError(error => {
|
.pipe(catchError(error => {
|
||||||
this.data = [];
|
this.data = [];
|
||||||
@ -153,35 +147,4 @@ export class ScheduleComponent {
|
|||||||
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;
|
|
||||||
}
|
}
|
||||||
|
82
src/services/token-refresh.service.ts
Normal file
82
src/services/token-refresh.service.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import {BehaviorSubject, catchError, of, Subject} from "rxjs";
|
||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {AuthService} from "@service/auth.service";
|
||||||
|
import {environment} from "@environment";
|
||||||
|
import ApiService from "@api/api.service";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class TokenRefreshService {
|
||||||
|
private tokenRefreshing$ = new BehaviorSubject<boolean>(false);
|
||||||
|
private refreshTokenTimeout: any;
|
||||||
|
private refreshTokenExpireMs: number = environment.retryDelay;
|
||||||
|
|
||||||
|
constructor(private authService: AuthService) {
|
||||||
|
this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startTokenRefresh(): void {
|
||||||
|
this.refreshTokenTimeout = setTimeout(() => {
|
||||||
|
this.refreshToken();
|
||||||
|
}, this.refreshTokenExpireMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshToken(): void {
|
||||||
|
if (this.tokenRefreshing$.value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.tokenRefreshing$.next(true);
|
||||||
|
|
||||||
|
this.authService.refreshToken()
|
||||||
|
.pipe(
|
||||||
|
catchError(error => {
|
||||||
|
if (error.status === 403 || error.status === 401 || !localStorage.getItem(ApiService.tokenKey)) {
|
||||||
|
localStorage.removeItem(ApiService.tokenKey);
|
||||||
|
return of(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
let retryTime = this.refreshTokenExpireMs;
|
||||||
|
|
||||||
|
if (retryTime < environment.retryDelay)
|
||||||
|
retryTime = environment.retryDelay;
|
||||||
|
|
||||||
|
// 15 minutes
|
||||||
|
if (retryTime * 2 <= 900_000)
|
||||||
|
retryTime *= 2;
|
||||||
|
else
|
||||||
|
retryTime = 900_000;
|
||||||
|
|
||||||
|
return of(retryTime);
|
||||||
|
}))
|
||||||
|
.subscribe(data => {
|
||||||
|
if (data)
|
||||||
|
this.setRefreshTokenExpireMs(data);
|
||||||
|
|
||||||
|
this.tokenRefreshing$.next(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTokenRefreshing$(): Subject<boolean> {
|
||||||
|
return this.tokenRefreshing$;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setRefreshTokenExpireMs(expireMs: number | string | Date | null = null): void {
|
||||||
|
let expireMsNumber: number;
|
||||||
|
if (expireMs === null)
|
||||||
|
expireMsNumber = -1;
|
||||||
|
else if (expireMs instanceof Date || typeof expireMs === 'string')
|
||||||
|
expireMsNumber = new Date(expireMs).getTime() - 1000 - Date.now();
|
||||||
|
else
|
||||||
|
expireMsNumber = expireMs;
|
||||||
|
|
||||||
|
if (expireMsNumber < environment.retryDelay)
|
||||||
|
expireMsNumber = environment.retryDelay;
|
||||||
|
|
||||||
|
this.refreshTokenExpireMs = expireMsNumber;
|
||||||
|
console.log('New refresh token interval:', this.refreshTokenExpireMs);
|
||||||
|
|
||||||
|
clearTimeout(this.refreshTokenTimeout);
|
||||||
|
this.startTokenRefresh();
|
||||||
|
}
|
||||||
|
}
|
37
src/shared/responses/v1/facultyDetailsResponse.ts
Normal file
37
src/shared/responses/v1/facultyDetailsResponse.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* MIREA Schedule Web API
|
||||||
|
* This API provides a convenient interface for retrieving data stored in the database. Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.
|
||||||
|
*
|
||||||
|
* OpenAPI spec version: 1.0
|
||||||
|
* Contact: support@winsomnia.net
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by the swagger code generator program.
|
||||||
|
* https://github.com/swagger-api/swagger-codegen.git
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents detailed information about a faculty.
|
||||||
|
*/
|
||||||
|
export interface FacultyDetailsResponse {
|
||||||
|
/**
|
||||||
|
* Gets or sets the unique identifier of the faculty.
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
/**
|
||||||
|
* Gets or sets the name of the faculty.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Gets or sets the unique identifier of the campus to which the faculty belongs (optional).
|
||||||
|
*/
|
||||||
|
campusId?: number;
|
||||||
|
/**
|
||||||
|
* Gets or sets the name of the campus to which the faculty belongs (optional).
|
||||||
|
*/
|
||||||
|
campusName?: string;
|
||||||
|
/**
|
||||||
|
* Gets or sets the code name of the campus to which the faculty belongs (optional).
|
||||||
|
*/
|
||||||
|
campusCode?: string;
|
||||||
|
}
|
@ -13,7 +13,7 @@
|
|||||||
/**
|
/**
|
||||||
* Represents basic information about a faculty.
|
* Represents basic information about a faculty.
|
||||||
*/
|
*/
|
||||||
export interface FacultyResponse {
|
export interface FacultyResponse {
|
||||||
/**
|
/**
|
||||||
* Gets or sets the unique identifier of the faculty.
|
* Gets or sets the unique identifier of the faculty.
|
||||||
*/
|
*/
|
||||||
@ -22,4 +22,8 @@ export interface FacultyResponse {
|
|||||||
* Gets or sets the name of the faculty.
|
* Gets or sets the name of the faculty.
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
}
|
/**
|
||||||
|
* Gets or sets the unique identifier of the campus to which the faculty belongs (optional).
|
||||||
|
*/
|
||||||
|
campusId?: number;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user